@4kk11/cooklang-sankey 0.1.0

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.
@@ -0,0 +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"],"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"]}
@@ -0,0 +1,408 @@
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
+
4
+ /**
5
+ * Cooklang Parser wrapper using official @cooklang/cooklang package.
6
+ *
7
+ * @remarks
8
+ * This module provides a thin wrapper around the official Cooklang parser,
9
+ * re-exporting types and adding metadata extraction utilities.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+
14
+ /**
15
+ * Metadata extracted from a parsed Cooklang recipe.
16
+ *
17
+ * @remarks
18
+ * Contains standard recipe metadata fields plus any custom metadata
19
+ * defined in the recipe using the `>> key: value` syntax.
20
+ */
21
+ interface RecipeMetadata {
22
+ /** Recipe title from `>> title:` */
23
+ title?: string;
24
+ /** Recipe description from `>> description:` */
25
+ description?: string;
26
+ /** Array of tags from `>> tags:` */
27
+ tags?: string[];
28
+ /** Formatted cooking time string (e.g., "prep: 10min, cook: 30min") */
29
+ cookingTime?: string;
30
+ /** Servings range or value (e.g., "4" or "4-6") */
31
+ servings?: string;
32
+ /** Additional custom metadata fields */
33
+ [key: string]: unknown;
34
+ }
35
+ /**
36
+ * Extracts metadata from a parsed Cooklang recipe.
37
+ *
38
+ * @param recipe - The parsed CooklangRecipe object
39
+ * @returns An object containing extracted metadata fields
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { CooklangParser } from "@cooklang/cooklang";
44
+ * import { extractMetadata } from "./parser";
45
+ *
46
+ * const parser = new CooklangParser();
47
+ * const [recipe] = parser.parse(`
48
+ * >> title: Pasta Carbonara
49
+ * >> servings: 4
50
+ * @pasta{400g} を茹でる
51
+ * `);
52
+ *
53
+ * const metadata = extractMetadata(recipe);
54
+ * // { title: "Pasta Carbonara", servings: "4" }
55
+ * ```
56
+ */
57
+ declare function extractMetadata(recipe: CooklangRecipe): RecipeMetadata;
58
+
59
+ /**
60
+ * Sankey diagram type definitions (framework-agnostic)
61
+ *
62
+ * @remarks
63
+ * These types are designed to be independent of any specific visualization library,
64
+ * allowing integration with D3.js, ECharts, or other charting libraries.
65
+ *
66
+ * @packageDocumentation
67
+ */
68
+ /**
69
+ * Category of a node in the Sankey diagram.
70
+ *
71
+ * - `ingredient` - Raw ingredients from the recipe
72
+ * - `process` - Cooking steps/processes
73
+ * - `final` - The completed dish
74
+ */
75
+ type NodeCategory = "ingredient" | "process" | "final";
76
+ /**
77
+ * Type of transformation occurring between nodes.
78
+ *
79
+ * - `cooking` - Heat-based transformation (frying, boiling, etc.)
80
+ * - `preparation` - Mechanical transformation (chopping, mixing, etc.)
81
+ * - `timing` - Time-based step (marinating, resting, etc.)
82
+ * - `combination` - Combining multiple ingredients
83
+ * - `completion` - Final step to complete the dish
84
+ */
85
+ type TransformationType = "cooking" | "preparation" | "timing" | "combination" | "completion";
86
+ /**
87
+ * Value extracted from an ingredient quantity.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * // Numeric value with unit
92
+ * const numericValue: ExtractedValue = {
93
+ * type: "number",
94
+ * value: 200,
95
+ * label: "200g"
96
+ * };
97
+ *
98
+ * // Text-only value (e.g., "a pinch")
99
+ * const textValue: ExtractedValue = {
100
+ * type: "text",
101
+ * label: "a pinch"
102
+ * };
103
+ * ```
104
+ */
105
+ type ExtractedValue = {
106
+ /** Indicates a numeric value was extracted */
107
+ type: "number";
108
+ /** The numeric portion of the quantity */
109
+ value: number;
110
+ /** Human-readable display label (e.g., "200g") */
111
+ label: string;
112
+ } | {
113
+ /** Indicates only text was available */
114
+ type: "text";
115
+ /** Human-readable display label */
116
+ label: string;
117
+ };
118
+ /**
119
+ * Metadata associated with a node.
120
+ */
121
+ interface BaseNodeMetadata {
122
+ /** Step number in the recipe (1-indexed) */
123
+ stepNumber?: number;
124
+ /** Name of the recipe section containing this node */
125
+ sectionName?: string;
126
+ /** Duration string for timer nodes (e.g., "5 minutes") */
127
+ timerDuration?: string;
128
+ /** Original index in the ingredient/step array */
129
+ originalIndex?: number;
130
+ }
131
+ /**
132
+ * Metadata associated with a link.
133
+ */
134
+ interface BaseLinkMetadata {
135
+ /** Step number where this link originates */
136
+ stepNumber?: number;
137
+ /** Type of transformation this link represents */
138
+ transformationType?: TransformationType;
139
+ }
140
+ /**
141
+ * Base structure for a Sankey diagram node.
142
+ */
143
+ interface BaseSankeyNode {
144
+ /** Unique identifier for the node */
145
+ id: string;
146
+ /** Display name of the node */
147
+ name: string;
148
+ /** Category of the node */
149
+ category: NodeCategory;
150
+ /** Normalized value for visualization sizing */
151
+ value: number;
152
+ /** Short label for display (e.g., step number, quantity) */
153
+ label: string;
154
+ /** Original value before normalization */
155
+ originalValue?: number;
156
+ }
157
+ /**
158
+ * Base structure for a Sankey diagram link.
159
+ */
160
+ interface BaseSankeyLink {
161
+ /** ID of the source node */
162
+ source: string;
163
+ /** ID of the target node */
164
+ target: string;
165
+ /** Flow value determining link width */
166
+ value: number;
167
+ /** Original value before normalization */
168
+ originalValue?: number;
169
+ }
170
+ /**
171
+ * Sankey node with optional metadata.
172
+ *
173
+ * @see {@link BaseSankeyNode} for base properties
174
+ * @see {@link BaseNodeMetadata} for metadata properties
175
+ */
176
+ interface SankeyNode extends BaseSankeyNode {
177
+ /** Additional node metadata */
178
+ metadata?: BaseNodeMetadata;
179
+ }
180
+ /**
181
+ * Sankey link with optional metadata.
182
+ *
183
+ * @see {@link BaseSankeyLink} for base properties
184
+ * @see {@link BaseLinkMetadata} for metadata properties
185
+ */
186
+ interface SankeyLink extends BaseSankeyLink {
187
+ /** Additional link metadata */
188
+ metadata?: BaseLinkMetadata;
189
+ }
190
+ /**
191
+ * Complete Sankey diagram data structure.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * const data: SankeyData = {
196
+ * nodes: [
197
+ * { id: "0", name: "flour", category: "ingredient", value: 1, label: "200g" },
198
+ * { id: "step_main_1", name: "Mix ingredients", category: "process", value: 1, label: "1" },
199
+ * { id: "final_dish", name: "Bread", category: "final", value: 1, label: "" }
200
+ * ],
201
+ * links: [
202
+ * { source: "0", target: "step_main_1", value: 1 },
203
+ * { source: "step_main_1", target: "final_dish", value: 1 }
204
+ * ]
205
+ * };
206
+ * ```
207
+ */
208
+ interface SankeyData {
209
+ /** Array of nodes in the diagram */
210
+ nodes: SankeyNode[];
211
+ /** Array of links connecting nodes */
212
+ links: SankeyLink[];
213
+ }
214
+ /**
215
+ * DAG (Directed Acyclic Graph) node with dependency tracking.
216
+ *
217
+ * @remarks
218
+ * Extends SankeyNode with input/output arrays for topological sorting
219
+ * and value propagation calculations.
220
+ *
221
+ * @see {@link SankeyNode} for base properties
222
+ */
223
+ interface DAGNode extends SankeyNode {
224
+ /** IDs of nodes that flow into this node */
225
+ inputs: string[];
226
+ /** IDs of nodes that this node flows to */
227
+ outputs: string[];
228
+ }
229
+ /**
230
+ * Edge in the DAG representing a flow between nodes.
231
+ */
232
+ interface DAGEdge {
233
+ /** ID of the source node */
234
+ from: string;
235
+ /** ID of the target node */
236
+ to: string;
237
+ /** Additional edge metadata */
238
+ metadata?: BaseLinkMetadata;
239
+ }
240
+
241
+ /**
242
+ * Sankey diagram data generator from Cooklang recipes.
243
+ *
244
+ * @remarks
245
+ * Main module for transforming parsed Cooklang recipes into
246
+ * Sankey diagram data structures. Orchestrates the DAG building,
247
+ * value calculation, and normalization pipeline.
248
+ *
249
+ * @packageDocumentation
250
+ */
251
+
252
+ /**
253
+ * Configuration options for Sankey diagram generation.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * const options: SankeyGeneratorOptions = {
258
+ * finalNodeName: "Carbonara",
259
+ * normalization: "logarithmic",
260
+ * minLinkValue: 0.1
261
+ * };
262
+ * ```
263
+ */
264
+ interface SankeyGeneratorOptions {
265
+ /**
266
+ * Display name for the final dish node.
267
+ * @defaultValue "完成品"
268
+ */
269
+ finalNodeName?: string;
270
+ /**
271
+ * Method for normalizing ingredient values.
272
+ * - `logarithmic`: Compress large ranges (recommended)
273
+ * - `linear`: Linear scaling
274
+ * - `none`: Use raw values
275
+ * @defaultValue "logarithmic"
276
+ */
277
+ normalization?: "logarithmic" | "linear" | "none";
278
+ /**
279
+ * Minimum value for links to ensure visibility.
280
+ * @defaultValue 0.1
281
+ */
282
+ minLinkValue?: number;
283
+ }
284
+ /**
285
+ * Generates Sankey diagram data from a parsed Cooklang recipe.
286
+ *
287
+ * @remarks
288
+ * This is the main entry point for the library. It transforms a parsed
289
+ * Cooklang recipe into a Sankey diagram data structure by:
290
+ * 1. Creating nodes for ingredients, steps, and the final dish
291
+ * 2. Building edges representing ingredient flow
292
+ * 3. Performing topological sort for correct value propagation
293
+ * 4. Normalizing values for balanced visualization
294
+ *
295
+ * @param recipe - A parsed CooklangRecipe object
296
+ * @param options - Optional configuration for generation
297
+ * @returns SankeyData structure, or null if generation fails
298
+ *
299
+ * @example
300
+ * ```ts
301
+ * import { CooklangParser, generateSankeyData } from 'cooklang-sankey';
302
+ *
303
+ * const parser = new CooklangParser();
304
+ * const [recipe] = parser.parse(`
305
+ * @pasta{400g} を茹でる。
306
+ * @卵{3個}と@チーズ{100g}を混ぜる。
307
+ * `);
308
+ *
309
+ * const data = generateSankeyData(recipe, {
310
+ * finalNodeName: "Carbonara",
311
+ * normalization: "logarithmic"
312
+ * });
313
+ * ```
314
+ */
315
+ declare const generateSankeyData: (recipe: CooklangRecipe, options?: SankeyGeneratorOptions) => SankeyData | null;
316
+ /**
317
+ * Validates and optimizes Sankey data for visualization.
318
+ *
319
+ * @remarks
320
+ * Performs three optimization steps:
321
+ * 1. **Remove invalid links**: Filters out links referencing non-existent nodes
322
+ * 2. **Remove orphaned nodes**: Keeps only nodes connected to valid links
323
+ * (final nodes are always preserved)
324
+ * 3. **Normalize link values**: Scales values relative to minimum for
325
+ * stable visualization (ensures all values >= 1)
326
+ *
327
+ * @param data - The SankeyData to optimize
328
+ * @returns A new SankeyData with optimizations applied
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * const rawData = generateSankeyData(recipe);
333
+ * if (rawData) {
334
+ * const optimized = optimizeSankeyData(rawData);
335
+ * // Use optimized data for visualization
336
+ * }
337
+ * ```
338
+ */
339
+ declare const optimizeSankeyData: (data: SankeyData) => SankeyData;
340
+
341
+ /**
342
+ * Value formatting utilities using official @cooklang/cooklang package.
343
+ *
344
+ * @remarks
345
+ * Provides utilities for converting Cooklang quantity and value types
346
+ * into human-readable string representations.
347
+ *
348
+ * @packageDocumentation
349
+ */
350
+
351
+ /**
352
+ * Formats a Cooklang Value to a string representation.
353
+ *
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
356
+ *
357
+ * @example
358
+ * ```ts
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); // ""
363
+ * ```
364
+ */
365
+ declare const formatValue: (value: Value | null | undefined) => string;
366
+ /**
367
+ * Formats a Cooklang Quantity into separate value and unit strings.
368
+ *
369
+ * @param quantity - The Quantity object to format
370
+ * @returns An object with `quantity` (numeric string) and `unit` (unit string)
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * formatQuantityAmount({ value: { type: "number", value: 200 }, unit: "g" });
375
+ * // { quantity: "200", unit: "g" }
376
+ *
377
+ * formatQuantityAmount(null);
378
+ * // { quantity: "", unit: "" }
379
+ * ```
380
+ */
381
+ declare const formatQuantityAmount: (quantity: Quantity | null | undefined) => {
382
+ quantity: string;
383
+ unit: string;
384
+ };
385
+ /**
386
+ * Generates complete text from step items by resolving references.
387
+ *
388
+ * @remarks
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
394
+ *
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
398
+ *
399
+ * @example
400
+ * ```ts
401
+ * // For a step with text "Cook " + ingredient(pasta, 400g) + " until done"
402
+ * generateStepText(step, recipe);
403
+ * // "Cook pasta(400g) until done"
404
+ * ```
405
+ */
406
+ declare const generateStepText: (step: Step, recipe: CooklangRecipe) => string;
407
+
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 };