@gram-lang/analyzer
Enriches a CompilationResult (from @gram-lang/kitchen) with physical properties — standardized mass, purchasing yield, nutrition estimates, and baker’s percentages — by cross-referencing an ingredient database. This is the only stage that needs a database; parsing and compilation work on any recipe with no external data.
analyze
Section titled “analyze”The ingredient database is the second positional argument, not a field on
options—analyze(compiled, database, options?).
analyze() is pure and never mutates result. AnalyzedCompilationResult extends CompilationResult — same shape, plus per-usage normalizedMass/conversionMethod/isEstimate/purchasingMass/bakersPercentage fields and a metrics.nutrition block. See Data Formats for a fully annotated example.
AnalyzerOptions
Section titled “AnalyzerOptions”All flags default to enabled (!== false checks internally) — pass false to opt out of a given enrichment pass.
| Option | Type | Description |
|---|---|---|
enableMassStandardization | boolean | Convert ingredient quantities into standardized grams. |
enableYieldCalculation | boolean | Apply the ingredient database’s physical.yield (waste factor) when standardizing mass. |
enableNutritionalEstimation | boolean | Compute metrics.nutrition (calories, macros, optionally per-portion). |
enableBakersMath | boolean | Compute bakersPercentage relative to the *-marked (or bakersReference) ingredient. |
bakersReference | string | Explicit ingredient id to use as the 100% baker’s-percentage base, instead of the * modifier. |
portions | number | Overrides the recipe’s own portions: frontmatter as the divisor for metrics.nutrition.perPortion. Omit to use what the recipe declares. |
lang | string | Optional locale code (e.g. 'en', 'fr') for locale-scoped unit normalization and category sorting. |
validateIngredientDatabase
Section titled “validateIngredientDatabase”Validates entry-by-entry rather than all-or-nothing: one malformed ingredient doesn’t prevent every other valid one from loading. Use data to compile/analyze; surface rejected to the user (e.g. gram db validate). See Data Formats for the IngredientData YAML schema.
Mass utilities
Section titled “Mass utilities”standardizeMass resolves a quantity to grams via, in order: (1) a direct mass unit (g, kg, oz…), (2) a volume unit converted through the ingredient’s resolved density, (3) an unrecognized unit (clove, slice…) treated as a count via the ingredient’s unit_weight. Returns null — never guesses — when a volume unit has no resolvable density.
convertUnit converts between two arbitrary unit strings, bridging mass↔volume families via density (g/mL) when given. Returns null for an unresolvable cross-family conversion without one.
Mass & volume conversion table
Section titled “Mass & volume conversion table”The base conversion factors standardizeMass/convertUnit use for same-family (mass↔mass, volume↔volume) conversions, before any density is involved:
| Family | Unit | Factor (relative to base) |
|---|---|---|
mass (base: g) | mg | 0.001 |
mass (base: g) | g | 1 |
mass (base: g) | kg | 1000 |
mass (base: g) | oz | 28.3495 |
mass (base: g) | lb | 453.592 |
mass (base: g) | livre | 500 |
volume (base: ml) | ml | 1 |
volume (base: ml) | cl | 10 |
volume (base: ml) | dl | 100 |
volume (base: ml) | l | 1000 |
volume (base: ml) | drop | 0.078 |
volume (base: ml) | smidgen | 0.156 |
volume (base: ml) | pinch | 0.3125 |
volume (base: ml) | dash | 0.625 |
volume (base: ml) | tad | 1.25 |
volume (base: ml) | tsp | 4.9289 |
volume (base: ml) | tbsp | 14.7868 |
volume (base: ml) | cup | 236.588 |
volume (base: ml) | tasse | 250 |
volume (base: ml) | pt | 473.176 |
volume (base: ml) | qt | 946.353 |
volume (base: ml) | gal | 3785.41 |
volume (base: ml) | fl oz | 29.5735 |
calculateNutrition
Section titled “calculateNutrition”Flattens composites and alternatives (taking the first option), skips optional-modifier and zero-quantity ingredients, and sums nutrition fields (declared per 100g in the database — see Data Formats) scaled by each ingredient’s standardized mass. Returns isEstimate: true if any contributing mass was itself an estimate, and coverage (0–1: fraction of ingredients with known nutrition data).
NutritionMetrics carries the same macro profile on up to three bases:
Every basis is derived from the same unrounded sums and rounded independently, so a per-portion figure never compounds the rounding of the total. basis is the denominator behind per100g — see Nutritional Estimation for why it matters and what “per 100 g” means when no cooking loss is modelled.
Which nutrients exist, their units and their rounding all come from a single exported NUTRIENTS table, so gram db enrich, gram db lint, gram db search, the editor hover and every renderer enumerate exactly the same set.
diffRecipes
Section titled “diffRecipes”Structural diff between two compiled recipes (e.g. two versions of the same file, or a scaled vs. unscaled result) — ingredient quantity/unit changes (with percentChange when comparable), preparation changes, timing deltas, added/removed/changed sections, temperature and timer changes, and frontmatter (meta) changes. hasChanges is true if any category is non-empty.
A section spliced in from an @use import (ProcessedSection.module set) is excluded from the section/preparation/temperature/timer comparisons — otherwise adding a single import would shift every other section’s position and look like the whole recipe changed. Its own import is compared separately instead, by (uri, binding, scale factor), in modules: ModuleDelta[] — a rescaled or re-bound import is reported as "changed".