@4kk11/cooklang-sankey 0.1.1 → 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 CHANGED
@@ -465,40 +465,6 @@ var optimizeSankeyData = (data) => {
465
465
  links: normalizedLinks
466
466
  };
467
467
  };
468
- function parseRecipeForDisplay(recipe) {
469
- const flatIngredients = cooklang.getFlatIngredients(recipe);
470
- const ingredients = flatIngredients.map((ing) => ({
471
- name: ing.name,
472
- quantity: ing.quantity !== null ? String(ing.quantity) : "",
473
- unit: ing.unit || ""
474
- }));
475
- const flatCookware = cooklang.getFlatCookware(recipe);
476
- const cookware = flatCookware.map((cw) => ({
477
- name: cw.name,
478
- note: cw.note || ""
479
- }));
480
- const flatTimers = cooklang.getFlatTimers(recipe);
481
- const timers = flatTimers.map((timer) => ({
482
- name: timer.name || "",
483
- quantity: timer.quantity !== null ? String(timer.quantity) : "",
484
- unit: timer.unit || "minutes"
485
- }));
486
- const steps = [];
487
- for (const section of recipe.sections) {
488
- for (const content of section.content) {
489
- if (content.type === "step") {
490
- const stepText = generateStepText(content.value, recipe);
491
- steps.push({ text: stepText });
492
- }
493
- }
494
- }
495
- return {
496
- ingredients,
497
- cookware,
498
- timers,
499
- steps
500
- };
501
- }
502
468
 
503
469
  Object.defineProperty(exports, "CooklangParser", {
504
470
  enumerable: true,
@@ -508,18 +474,6 @@ Object.defineProperty(exports, "CooklangRecipe", {
508
474
  enumerable: true,
509
475
  get: function () { return cooklang.CooklangRecipe; }
510
476
  });
511
- Object.defineProperty(exports, "getFlatCookware", {
512
- enumerable: true,
513
- get: function () { return cooklang.getFlatCookware; }
514
- });
515
- Object.defineProperty(exports, "getFlatIngredients", {
516
- enumerable: true,
517
- get: function () { return cooklang.getFlatIngredients; }
518
- });
519
- Object.defineProperty(exports, "getFlatTimers", {
520
- enumerable: true,
521
- get: function () { return cooklang.getFlatTimers; }
522
- });
523
477
  Object.defineProperty(exports, "getNumericValue", {
524
478
  enumerable: true,
525
479
  get: function () { return cooklang.getNumericValue; }
@@ -534,6 +488,5 @@ exports.formatValue = formatValue;
534
488
  exports.generateSankeyData = generateSankeyData;
535
489
  exports.generateStepText = generateStepText;
536
490
  exports.optimizeSankeyData = optimizeSankeyData;
537
- exports.parseRecipeForDisplay = parseRecipeForDisplay;
538
491
  //# sourceMappingURL=index.cjs.map
539
492
  //# sourceMappingURL=index.cjs.map
@@ -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","getNumericValue","quantity_display","grouped_quantity_is_empty","grouped_quantity_display","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 * 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","/**\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
1
  import { CooklangRecipe, Value, Quantity, Step } from '@cooklang/cooklang';
2
- export { Content, CooklangParser, CooklangRecipe, FlatCookware, FlatIngredient, FlatTimer, Ingredient, Item, Quantity, Section, Step, Value, getFlatCookware, getFlatIngredients, getFlatTimers, getNumericValue, quantity_display } 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.
@@ -405,146 +405,4 @@ declare const formatQuantityAmount: (quantity: Quantity | null | undefined) => {
405
405
  */
406
406
  declare const generateStepText: (step: Step, recipe: CooklangRecipe) => string;
407
407
 
408
- /**
409
- * Display-ready recipe types for UI rendering.
410
- *
411
- * @remarks
412
- * These types represent formatted recipe data suitable for direct UI display.
413
- * They use simple string values for quantities and units, making them easy
414
- * to render without additional formatting logic.
415
- *
416
- * @packageDocumentation
417
- */
418
- /**
419
- * Ingredient formatted for display.
420
- *
421
- * @example
422
- * ```ts
423
- * const ingredient: DisplayIngredient = {
424
- * name: "小麦粉",
425
- * quantity: "200",
426
- * unit: "g"
427
- * };
428
- * ```
429
- */
430
- interface DisplayIngredient {
431
- readonly name: string;
432
- readonly quantity: string;
433
- readonly unit: string;
434
- }
435
- /**
436
- * Cookware formatted for display.
437
- *
438
- * @example
439
- * ```ts
440
- * const cookware: DisplayCookware = {
441
- * name: "フライパン",
442
- * note: "26cm"
443
- * };
444
- * ```
445
- */
446
- interface DisplayCookware {
447
- readonly name: string;
448
- readonly note: string;
449
- }
450
- /**
451
- * Timer formatted for display.
452
- *
453
- * @example
454
- * ```ts
455
- * const timer: DisplayTimer = {
456
- * name: "煮込み",
457
- * quantity: "30",
458
- * unit: "分"
459
- * };
460
- * ```
461
- */
462
- interface DisplayTimer {
463
- readonly name: string;
464
- readonly quantity: string;
465
- readonly unit: string;
466
- }
467
- /**
468
- * Step formatted for display.
469
- *
470
- * @example
471
- * ```ts
472
- * const step: DisplayStep = {
473
- * text: "小麦粉(200g)と卵(2個)を混ぜる"
474
- * };
475
- * ```
476
- */
477
- interface DisplayStep {
478
- readonly text: string;
479
- }
480
- /**
481
- * Complete recipe data formatted for UI display.
482
- *
483
- * @remarks
484
- * This interface represents a fully parsed and formatted recipe
485
- * ready for rendering in a UI component. All values are strings
486
- * for easy display without additional formatting.
487
- *
488
- * @example
489
- * ```ts
490
- * import { parseRecipeForDisplay } from '@4kk11/cooklang-sankey';
491
- *
492
- * const displayData = parseRecipeForDisplay(recipe);
493
- * displayData.ingredients.forEach(ing => {
494
- * console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);
495
- * });
496
- * ```
497
- */
498
- interface DisplayRecipe {
499
- readonly ingredients: readonly DisplayIngredient[];
500
- readonly cookware: readonly DisplayCookware[];
501
- readonly timers: readonly DisplayTimer[];
502
- readonly steps: readonly DisplayStep[];
503
- }
504
-
505
- /**
506
- * Recipe display utilities for UI rendering.
507
- *
508
- * @remarks
509
- * Provides utilities for converting parsed CooklangRecipe objects into
510
- * display-ready data structures suitable for UI rendering.
511
- *
512
- * Uses the official @cooklang/cooklang package's convenience functions
513
- * for accurate quantity aggregation and formatting.
514
- *
515
- * @packageDocumentation
516
- */
517
-
518
- /**
519
- * Parses a CooklangRecipe into a display-ready format.
520
- *
521
- * @remarks
522
- * Uses the official @cooklang/cooklang package's convenience functions
523
- * (`getFlatIngredients`, `getFlatCookware`, `getFlatTimers`) for proper
524
- * handling of grouped quantities.
525
- *
526
- * @param recipe - The parsed CooklangRecipe object
527
- * @returns DisplayRecipe with formatted data for UI rendering
528
- *
529
- * @example
530
- * ```ts
531
- * import { CooklangParser, parseRecipeForDisplay } from '@4kk11/cooklang-sankey';
532
- *
533
- * const parser = new CooklangParser();
534
- * const [recipe] = parser.parse(recipeText);
535
- * const displayData = parseRecipeForDisplay(recipe);
536
- *
537
- * // Render ingredients
538
- * displayData.ingredients.forEach(ing => {
539
- * console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);
540
- * });
541
- *
542
- * // Render steps
543
- * displayData.steps.forEach((step, i) => {
544
- * console.log(`${i + 1}. ${step.text}`);
545
- * });
546
- * ```
547
- */
548
- declare function parseRecipeForDisplay(recipe: CooklangRecipe): DisplayRecipe;
549
-
550
- export { type BaseLinkMetadata, type BaseNodeMetadata, type BaseSankeyLink, type BaseSankeyNode, type DAGEdge, type DAGNode, type DisplayCookware, type DisplayIngredient, type DisplayRecipe, type DisplayStep, type DisplayTimer, type ExtractedValue, type NodeCategory, type RecipeMetadata, type SankeyData, type SankeyGeneratorOptions, type SankeyLink, type SankeyNode, type TransformationType, extractMetadata, formatQuantityAmount, formatValue, generateSankeyData, generateStepText, optimizeSankeyData, parseRecipeForDisplay };
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
1
  import { CooklangRecipe, Value, Quantity, Step } from '@cooklang/cooklang';
2
- export { Content, CooklangParser, CooklangRecipe, FlatCookware, FlatIngredient, FlatTimer, Ingredient, Item, Quantity, Section, Step, Value, getFlatCookware, getFlatIngredients, getFlatTimers, getNumericValue, quantity_display } 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.
@@ -405,146 +405,4 @@ declare const formatQuantityAmount: (quantity: Quantity | null | undefined) => {
405
405
  */
406
406
  declare const generateStepText: (step: Step, recipe: CooklangRecipe) => string;
407
407
 
408
- /**
409
- * Display-ready recipe types for UI rendering.
410
- *
411
- * @remarks
412
- * These types represent formatted recipe data suitable for direct UI display.
413
- * They use simple string values for quantities and units, making them easy
414
- * to render without additional formatting logic.
415
- *
416
- * @packageDocumentation
417
- */
418
- /**
419
- * Ingredient formatted for display.
420
- *
421
- * @example
422
- * ```ts
423
- * const ingredient: DisplayIngredient = {
424
- * name: "小麦粉",
425
- * quantity: "200",
426
- * unit: "g"
427
- * };
428
- * ```
429
- */
430
- interface DisplayIngredient {
431
- readonly name: string;
432
- readonly quantity: string;
433
- readonly unit: string;
434
- }
435
- /**
436
- * Cookware formatted for display.
437
- *
438
- * @example
439
- * ```ts
440
- * const cookware: DisplayCookware = {
441
- * name: "フライパン",
442
- * note: "26cm"
443
- * };
444
- * ```
445
- */
446
- interface DisplayCookware {
447
- readonly name: string;
448
- readonly note: string;
449
- }
450
- /**
451
- * Timer formatted for display.
452
- *
453
- * @example
454
- * ```ts
455
- * const timer: DisplayTimer = {
456
- * name: "煮込み",
457
- * quantity: "30",
458
- * unit: "分"
459
- * };
460
- * ```
461
- */
462
- interface DisplayTimer {
463
- readonly name: string;
464
- readonly quantity: string;
465
- readonly unit: string;
466
- }
467
- /**
468
- * Step formatted for display.
469
- *
470
- * @example
471
- * ```ts
472
- * const step: DisplayStep = {
473
- * text: "小麦粉(200g)と卵(2個)を混ぜる"
474
- * };
475
- * ```
476
- */
477
- interface DisplayStep {
478
- readonly text: string;
479
- }
480
- /**
481
- * Complete recipe data formatted for UI display.
482
- *
483
- * @remarks
484
- * This interface represents a fully parsed and formatted recipe
485
- * ready for rendering in a UI component. All values are strings
486
- * for easy display without additional formatting.
487
- *
488
- * @example
489
- * ```ts
490
- * import { parseRecipeForDisplay } from '@4kk11/cooklang-sankey';
491
- *
492
- * const displayData = parseRecipeForDisplay(recipe);
493
- * displayData.ingredients.forEach(ing => {
494
- * console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);
495
- * });
496
- * ```
497
- */
498
- interface DisplayRecipe {
499
- readonly ingredients: readonly DisplayIngredient[];
500
- readonly cookware: readonly DisplayCookware[];
501
- readonly timers: readonly DisplayTimer[];
502
- readonly steps: readonly DisplayStep[];
503
- }
504
-
505
- /**
506
- * Recipe display utilities for UI rendering.
507
- *
508
- * @remarks
509
- * Provides utilities for converting parsed CooklangRecipe objects into
510
- * display-ready data structures suitable for UI rendering.
511
- *
512
- * Uses the official @cooklang/cooklang package's convenience functions
513
- * for accurate quantity aggregation and formatting.
514
- *
515
- * @packageDocumentation
516
- */
517
-
518
- /**
519
- * Parses a CooklangRecipe into a display-ready format.
520
- *
521
- * @remarks
522
- * Uses the official @cooklang/cooklang package's convenience functions
523
- * (`getFlatIngredients`, `getFlatCookware`, `getFlatTimers`) for proper
524
- * handling of grouped quantities.
525
- *
526
- * @param recipe - The parsed CooklangRecipe object
527
- * @returns DisplayRecipe with formatted data for UI rendering
528
- *
529
- * @example
530
- * ```ts
531
- * import { CooklangParser, parseRecipeForDisplay } from '@4kk11/cooklang-sankey';
532
- *
533
- * const parser = new CooklangParser();
534
- * const [recipe] = parser.parse(recipeText);
535
- * const displayData = parseRecipeForDisplay(recipe);
536
- *
537
- * // Render ingredients
538
- * displayData.ingredients.forEach(ing => {
539
- * console.log(`${ing.name}: ${ing.quantity}${ing.unit}`);
540
- * });
541
- *
542
- * // Render steps
543
- * displayData.steps.forEach((step, i) => {
544
- * console.log(`${i + 1}. ${step.text}`);
545
- * });
546
- * ```
547
- */
548
- declare function parseRecipeForDisplay(recipe: CooklangRecipe): DisplayRecipe;
549
-
550
- export { type BaseLinkMetadata, type BaseNodeMetadata, type BaseSankeyLink, type BaseSankeyNode, type DAGEdge, type DAGNode, type DisplayCookware, type DisplayIngredient, type DisplayRecipe, type DisplayStep, type DisplayTimer, type ExtractedValue, type NodeCategory, type RecipeMetadata, type SankeyData, type SankeyGeneratorOptions, type SankeyLink, type SankeyNode, type TransformationType, extractMetadata, formatQuantityAmount, formatValue, generateSankeyData, generateStepText, optimizeSankeyData, parseRecipeForDisplay };
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 { getNumericValue, quantity_display, getFlatIngredients, getFlatCookware, getFlatTimers, grouped_quantity_is_empty, grouped_quantity_display } from '@cooklang/cooklang';
2
- export { CooklangParser, CooklangRecipe, getFlatCookware, getFlatIngredients, getFlatTimers, getNumericValue, quantity_display } from '@cooklang/cooklang';
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
@@ -460,41 +460,7 @@ var optimizeSankeyData = (data) => {
460
460
  links: normalizedLinks
461
461
  };
462
462
  };
463
- function parseRecipeForDisplay(recipe) {
464
- const flatIngredients = getFlatIngredients(recipe);
465
- const ingredients = flatIngredients.map((ing) => ({
466
- name: ing.name,
467
- quantity: ing.quantity !== null ? String(ing.quantity) : "",
468
- unit: ing.unit || ""
469
- }));
470
- const flatCookware = getFlatCookware(recipe);
471
- const cookware = flatCookware.map((cw) => ({
472
- name: cw.name,
473
- note: cw.note || ""
474
- }));
475
- const flatTimers = getFlatTimers(recipe);
476
- const timers = flatTimers.map((timer) => ({
477
- name: timer.name || "",
478
- quantity: timer.quantity !== null ? String(timer.quantity) : "",
479
- unit: timer.unit || "minutes"
480
- }));
481
- const steps = [];
482
- for (const section of recipe.sections) {
483
- for (const content of section.content) {
484
- if (content.type === "step") {
485
- const stepText = generateStepText(content.value, recipe);
486
- steps.push({ text: stepText });
487
- }
488
- }
489
- }
490
- return {
491
- ingredients,
492
- cookware,
493
- timers,
494
- steps
495
- };
496
- }
497
463
 
498
- export { extractMetadata, formatQuantityAmount, formatValue, generateSankeyData, generateStepText, optimizeSankeyData, parseRecipeForDisplay };
464
+ export { extractMetadata, formatQuantityAmount, formatValue, generateSankeyData, generateStepText, optimizeSankeyData };
499
465
  //# sourceMappingURL=index.js.map
500
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":["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","/**\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4kk11/cooklang-sankey",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Generate Sankey diagram data from Cooklang recipes",
5
5
  "type": "module",
6
6
  "exports": {