Skip to content

Food Attributes — nutrition, allergens, and shelf-life off the recipe tree

What this covers. Beyond how much of something you have and what it cost, the food itself has facts: how many calories it carries, which allergens it contains, and how long it stays safe to eat. This chapter explains those three food attributes — nutrition, allergens, and shelf-life — and the one idea that unifies them: all three are computed by walking the same recipe tree that costing walks. Costing and food attributes remain separate consumers of that tree, but they share the same recipe-version and change-signal spine. The food-attribute rollup itself writes nutrition, allergens, and a shelf-life cap together. This chapter also covers the two reasons a made item's nutrition concentration shifts when you cook it, the two-stage way a safe-keeping window is worked out (decide what kind of food it is, then let a cited food-safety rulebook decide the days), how allergens accumulate up the tree, and how shelf-life informs receiving facts and the operator's recognition of expired food as waste.


1. A food attribute is not a stock fact — and the one place they meet

A food attribute answers a question about what the food is: its calories per 100 grams, the allergens it contains, how many days it keeps. That is a different kind of fact from how much you have on hand and what it is worth — which is the business of the stock ledger (the running journal of everything that moves in and out of a warehouse; see Stock ledger).

Keeping the two apart matters. An item's calorie count must not wobble because flour went up in price; an on-hand balance must not drift because someone corrected a nutrition label. So the firm rule is:

Food attributes move no stock. On-hand and value are independent of nutrition, allergens, and shelf-life. A food's use-by date informs handling; the quantity the operator actually discards becomes a waste write-off (§6).

The nutrition figures, the allergen list, the safe-keeping window, and the trip down the recipe that computes them are product information layered on the catalog. Receiving establishes the food's use-by date; a waste write-off records the quantity and value lost. The rest of this chapter teaches the product information; §6 connects it to receiving and waste.


2. The shared recipe tree — one model, aligned refreshes

A restaurant's recipes form a tree. A made item is composed of other items; some of those ingredients are themselves things the restaurant makes and stocks in advance — a batch of dough, a base sauce, a portion of cooked chicken. Such a made-in-advance stockable sub-recipe is a prep item — formally, a semi-finished good. A prep item has its own recipe, whose ingredients may in turn be other prep items, and so on down to the raw items you actually buy — the leaves of the tree.

To work out what a made item costs, you walk that tree from its recipe down to its raw leaves, multiply each leaf by how much of it the recipe uses, and add it all up. The textbook name for unpacking a recipe into its raw components this way is a bill-of-materials explosion. Costing already does this walk — carefully: it uses the version of each recipe that was in force on the relevant date, and it remembers intermediate results so a shared sub-recipe isn't recomputed for every item that uses it.

The key insight of this chapter is that cost is not the only thing that rolls up that tree. The same recipe tree supports both costing and food attributes. The food-attribute rollup walks the tree at recipe-line grain and writes three answers together; costing is the sibling calculation that uses the same recipe version and yield facts:

Answer What each leaf contributes How it combines going up
Cost the leaf's average cost sum the input costs, divide by the batch's output
Nutrition the leaf's per-100g profile sum the nutrient grams, normalize to the batch's output weight
Allergens the leaf's declared allergens take the union — every allergen any component carries
Shelf-life cap the leaf's remaining safe life take the minimum — the shortest-lived component sets the ceiling

A recipe matrix offers more than one possible build, so product-level food attributes need one stable reading. They use the base build: fixed and removable lines are present, addable lines are absent, and slots are empty. Cost may instead price a concrete build, but nutrition, allergens, and shelf-life stay one canonical product statement, deliberately independent of any particular order. This difference chooses a reading; it does not create a second tree.

Two consequences make this the whole point.

Recipe changes refresh the same affected graph. When a recipe — or any sub-recipe beneath it — changes, the same "the things above this are now out of date" seam feeds the cost and food-attribute consumers. Leaf profile changes and consumed-mass edits are food-attribute-only triggers, because they do not change stock value. There is no separate allergen tree or nutrition tree to drift from the recipe structure operators actually use.

The yield is computed once, reused everywhere. A prep item's recipe says how many portions a batch makes and how big each portion is; their product is the batch's output — how much the batch yields. That single number is cost's divisor (cost per unit of output) and nutrition's denominator (nutrients per 100 grams of output). The figure that makes food cost accurate is the same figure that makes calories-per-100g accurate. There is no second notion of yield; where a matrix needs a canonical yield, it uses the base build for the same deterministic reason.

The rest of this chapter specifies what each leaf contributes and how it combines — the columns of that table — for each of the three food attributes. The walk follows the recipe versioning and cycle-safety rules described in Recipes & production.


3. Nutrition

3.1 Any item can carry a profile — there is no "is it food?" gate

A nutrition profile is the set of nutrient figures for an item. Any item may carry one — not only obvious foods. A can of Coca-Cola bought purely for resale has calories and sugar just as much as a sack of flour does, and an operator may legitimately want those figures, so nutrition is ungated: there is no "eatable" switch on an item that decides whether it is allowed a profile.

This is deliberate. Gating nutrition on a category label would tie hard food data to a user-editable, translated word — exactly the kind of soft setting the model refuses to branch behavior on. Instead, a profile attaches to an item directly.

An item's nutrition is kept as history with one current entry: profiles are added over time, never overwritten, and exactly one is explicitly marked as the authoritative current one. (Picking "the newest one" by creation time is brittle — a bulk re-import or a clock running slightly off could silently flip which profile is treated as authoritative. An explicit current-marker removes that ambiguity.) Each profile records where it came from — typed in by hand, read off a label, matched to a public food database, or computed from the recipe — together with the reasoning and any warnings behind that choice.

3.2 The profile: a full nutrient set, on the basis the item declares

The basis of a recipe-derived or manual statement follows the item's recipe output facts, never its type, category, or stock behavior. A derived statement follows the version it computes; a manual statement follows the recipe active when the statement is made:

  • where that recipe declares a positive number of portions, the statement is per serving — computed figures divide the batch nutrients by those portions, while manual figures are stated for one portion;
  • otherwise, the statement is per 100 grams — computed figures normalize the batch nutrients by its weighed output, while manual figures are stated directly on that basis.

This means a prep item and a made-to-order item follow the same rule. A made-to-order plate normally declares portions and reads "612 kcal per serving"; a sauce produced into a weighed pool normally reads per 100 g. The basis is a fact about the output the recipe describes, not a proxy for the word dish.

Imported label and public-reference facts remain per 100 g source facts. They are the normalized evidence used at the leaves and are not rewritten merely because an item with a portioned recipe later derives a per-serving result. Source evidence and the recipe-derived statement answer different questions and remain legible as such.

A per-serving statement needs one more step when that item becomes an ingredient: the amount entering the parent must be translated back into servings. For a recipe-derived statement, that translation follows the exact recipe version recorded on the statement, even if another version is now active. A manual statement has no recipe-version anchor, so it follows the item's recipe active on the date being calculated. The declared portion size may supply a directly comparable amount; otherwise the recipe must relate each serving to grams through its output weight or a gram-based portion size. If neither route yields an honest conversion, the item remains named as missing a gram-equivalent and the whole computed statement above it is withheld. One serving is never guessed, and per-serving figures are never treated as per-100-gram facts.

The nutrient set is the full one a food-service operator needs, not just the headline macros. As well as calories, protein, carbohydrate, and fat, a profile carries sodium, fiber, sugars, saturated fat, and cholesterol. Keeping the full set prevents a source that knows all nine from becoming an apparently complete statement that silently omitted five.

An item counted by piece or volume can still support per-100-gram nutrition; its recipe must declare a batch output weight so those pieces or milliliters can be related to grams. Without that weight there is no honest denominator. A per-serving result instead needs the portion count. Each basis therefore withholds the result when its own denominator is unknown.

Computed nutrition is all-or-nothing. A recipe-derived item gets figures only when every ingredient it draws is fully profiled — all nine nutrients at each leaf, every intermediate weighable where weight is the basis, and a known portion count where servings are the basis. If one branch is incomplete, the item makes no computed nutrition statement and names the missing facts. A confident partial such as "Calories 540, Protein 32 g, Cholesterol unknown" looks more complete than it is; an honest absence is safer.

Declaring nutrition is optional. A manual profile is a complete assertion on its own and may remain authoritative while the computed alternative waits on missing composition facts.

One canonical set, rendered per region. Different jurisdictions want the same facts dressed differently, and the answer is never a second stored profile. The EU's nutrition declaration is the clearest case: it declares salt, not sodium — derived as salt = sodium × 2.5 — and states energy in kilojoules alongside kilocalories, derived from the stored kcal figure. Both are render-time arithmetic on the canonical sodium and calorie values; nothing extra is stored. Cholesterol runs the other way: it stays in the stored set (a US label uses it), but it is not a permitted nutrient on an EU declaration, so an EU rendering simply leaves it out — the data is universal, the label is regional.

3.3 The two-axis yield — why cooking changes the numbers

This is the subtle, correct part of computing nutrition up a recipe tree, and it is worth slowing down on. When you cook a batch, its nutrition concentration (nutrients per 100 grams) changes for two independent reasons. They answer different questions and must not be collapsed into one.

Axis one — the whole batch gains or loses water. Cooking moves water in or out of the entire batch, which changes the per-100g denominator: the batch's output weight. If 100 grams of dry ingredients cook up to 250 grams because they absorb water, every nutrient is now spread over 2.5 times the mass, so concentration falls to 40% of the dry figure. If a sauce reduces from 100 grams to 70 grams through evaporation, concentration rises. This output weight is the same output the batch's cost is divided by (§2).

Worked example (output weight). 100 g of dry pasta at 350 kcal per 100 g is cooked to an output weight of 250 g. The finished pasta is 350 × 100 ÷ 250 = 140 kcal per 100 g. Same nutrients, more mass, lower concentration.

Axis two — only part of an ingredient ends up in the food. Separately from the whole-batch water change, an individual ingredient may contribute more or less mass to the food than the recipe line says. Frying oil is the classic "more" case — a line might put 500 grams of oil in the fryer, but only the absorbed portion rides into the food. A drained brine or marinade is the classic "less" case — the recipe soaks the food in it, then pours it away. Other examples: breading that the food picks up, the edible mass left after trimming or deboning, and aromatics that are cooked in for flavor and then removed (which contribute nothing). For nutrition math, each line can state the mass actually consumed into the food, which overrides the recipe quantity when the two differ.

Worked example (consumed mass). A pickle recipe lists 200 g of brine, but the brine is drained off. Its consumed mass is 0 g, so it adds nothing to either the nutrient total or the output weight. Frying oil is the mirror image: a line of 500 g of oil in the fryer with a consumed mass of 15 g means only those 15 absorbed grams count toward the food's nutrition.

A third notion of yield — edible-portion vs as-purchased yield — belongs to costing, not nutrition, and the three must be kept straight:

  • Cost-yield (the costing chapter, Recipes & production) is about value: a carrot peeled from 124 g to 100 g concentrates its cost onto the 100 g that survive, raising their cost per gram.
  • Output weight is about the mass of the whole batch: cooking water in or out.
  • Consumed mass is about the mass of one ingredient that actually enters the food: drained brine, absorbed oil.

They are three different questions. The food-database matcher in §3.4 is even told not to fold cost-style cooking yields into the nutrient figures it picks — those are recipe-level consumed-mass and output-weight decisions handled here, not on the reference data. The full arithmetic — boiled water, evaporation, drained brine, absorbed frying oil, the salt that leaves with the pasta water — is worked through end to end in Nutrition math.

3.4 Where a leaf item's profile comes from — four sources

A leaf item (a raw bought thing) gets its profile from one of four sources; the recipe walk (§2) then derives every profile above it.

  • Barcode. Scanning a packaged item's barcode resolves it by exact match in a public food database of branded products — no guessing involved. The first time a given barcode is seen it is looked up on the spot and remembered, so scanning the same product again is instant. Scan the package, get the profile.
  • Public food-database match. For an unbranded ingredient, a careful match-from-a-shortlist approach is used: a plain text search first narrows the database to a small set of plausible candidates, and only then is the best one chosen from that shortlist — never an entry that wasn't on the list. This guards against an automated assistant inventing a match that doesn't exist. The chosen entry is steered toward the raw, canonical form of the ingredient rather than a branded prepared product, with no cooking yields baked in.
  • Label image. A photograph of a nutrition panel is read against a strict expected shape and normalized from per-serving to per-100g using the serving size on the label. For items counted by the piece or measured by volume, the grams-per-unit needed to convert is derived from the numbers visible on the label.
  • By hand. The operator simply types the figures in.

The two food-data sources behave differently, because barcodes and unbranded ingredients are different problems. Branded products number in the millions and are sharply regional — there is no compact, complete set to keep on hand, and the odds that any one supermarket's barcode already sits in a fixed table are poor. So a barcode is looked up the moment it is scanned, against a public database with genuine European product and barcode coverage (Open Food Facts is the kind of source meant), and the answer is kept locally — the first scan of a product fetches it, every later scan of the same product is instant. Unbranded raw foods are the opposite: a small, universal set, so they live in a compact, curated local table of canonical raw foods (the US government's reference food data is the kind of source meant) — raw-commodity nutrition does not vary by country, so one clean table serves every region, and the shortlist search behaves well precisely because the table it searches is small. Either way, an entry missing core nutrients is refused: a product whose nutrition facts aren't filled in is treated as not found, never stored half-blank.

3.5 Computed profiles carry their provenance too

When a profile is computed by rolling up a recipe for any made item, it is stamped as recipe-derived, with when it was computed and which version of the recipe it came from. Recording provenance only on hand-sourced leaf profiles isn't enough: without the version stamp on a computed profile you cannot tell a recipe-derived figure from a hand-entered one, nor whether it has fallen out of date. That version stamp is exactly what lets the single refresh-signal (§2) recognize that a computed profile predates the recipe it claims to describe, and recompute it. The stamp is a pointer, not the statement: a profile whose version is later deleted keeps its figures and its history place and simply loses the stamp — which the refresh-signal reads as out of date.


4. Allergens

An allergen is an ingredient that can trigger a dangerous reaction in some people — egg, milk, peanuts, shellfish, and so on. Mislabeling them is a legal and food-safety failure, not a cosmetic one, so a made item's allergen list must be correct by construction, not an educated guess.

Allergens therefore roll up the same recipe tree as everything else (§2):

  • Declared at the leaves. A raw item's allergens come from the item itself, from the ingredient text on its public-database entry, or from a label.
  • Unioned up the tree. Every made item inherits the union of the allergens of everything in the build used for food attributes. If any component anywhere beneath that build carries an allergen, the parent item carries it.
  • The union ignores consumed mass — on purpose. This is the one place allergens diverge from nutrition (§3.3). For nutrition, an ingredient whose consumed mass is zero — a drained brine, aromatics cooked in and lifted out — contributes nothing. For allergens it still contributes everything: a drained marinade or a discarded aromatic touched the food, so its allergens are kept and roll up regardless of how little mass remained. The rule is deliberately safety-conservative — the cost of over-declaring an allergen is an inconvenience, the cost of dropping one is a hospital trip — so the union never lets a near-zero mass erase a "contains."
  • Screened against a jurisdiction's fixed list — recorded with the statement. A regulator never asks "what allergens are in this product?" in the abstract; it asks against a fixed list. EU food-information law (Regulation 1169/2011) regulates fourteen allergens and makes the information legally required at the point of sale even for non-prepacked food — a plate served across a counter, not just a packaged product, so a restaurant must be able to answer "does this contain celery?" for every product it sells; the US names a different nine. A restaurant is screened against the list its country uses, the same country-by-country posture the system takes for tax. The list a statement was screened against is part of the statement, not re-decided each time it is read: an allergen attestation made today stays legible exactly as it was made even if the business later changes the country it operates in. The stored facts are the one canonical set; the standard is only the lens they were read through, frozen alongside them.
  • "Present" and "may contain" stay distinct. A declared allergen ("contains egg") is a different state from a cross-contact risk ("may contain traces of egg"), and they roll up separately: a "may contain" on a component does not silently get upgraded to "present" in the parent item above it.
  • Refreshed automatically. Like nutrition, an allergen list carries its provenance and is marked stale by the same single signal when a recipe changes. Nobody re-guesses a made item; the next walk simply reflects the change.
  • A hand declaration is honored, but never allowed to hide. An operator may pin a hand-entered allergen list as an item's truth; the computed union still runs underneath, and if the recipe comes to contain an allergen the hand list omits, that divergence is surfaced on the allergen panel. The hand entry stays — the operator owns it — but the gap is shown, because the one direction that can harm a customer (the recipe has it, the label doesn't) must never be silent.

Computing a made item's allergen set as the union of its actual ingredients' declared allergens is correct the moment the ingredients are right. Summarizing a recipe's text to guess its allergens is a guess that drifts the instant a sub-recipe changes underneath it. So where an automated assistant helps at all, its job is to help declare a leaf's allergens — read a label, match a database entry — never to infer the finished-item total.

Worked example. A burger-sauce prep item contains house mayonnaise, which contains egg. Egg is declared on the egg leaf; it unions into the mayonnaise, then into the burger sauce, then into every made item that uses that sauce — automatically, with the egg leaf recorded as the reason. Replace the mayonnaise with a vegan one and put it in force: the next walk drops egg from all of them, with no per-item re-guessing.


5. Shelf-life and food safety

Shelf-life is how long a food stays safe (and good) to keep. This is the food attribute most bound to actual food-safety standards, and the most important to get right, because the number an operator types in by habit is often wrong in a dangerous direction.

5.1 Two stages: classify the food, then let a cited rulebook decide the days

A produced item's shelf-life is worked out from how it is made, in two stages that are kept strictly separate.

Stage one — classify the food into risk flags. An automated reader reads the recipe — its structured ingredients and its free-form preparation steps — and emits risk flags from a fixed vocabulary: what kind of ingredients are involved (raw poultry, dairy, cooked rice or pasta, a component submerged in oil, …), how it is prepared (cooked, assembled raw, marinated raw, cured, …), and what process it goes through (held hot before chilling, rapidly chilled, vacuum-packed, …). Crucially, this stage is forbidden to produce a day number. Its only job is to answer "what kind of food is this?"

Stage two — a deterministic rulebook turns flags into days. A food-safety policy table maps those flags to a recommended number of days and a hard maximum. It is evaluated most-restrictive-rule first, first match wins, and every rule cites its food-safety source — national food codes for ready-to-eat time/temperature-controlled foods, government storage charts for raw meats and poultry, cooling-rate rules, and public guidance on specific hazards such as garlic in oil or cooled rice. The safety numbers live entirely in this auditable, testable rulebook — never in the automated reader's head.

The split is the whole point: it confines the automated part to perception ("this is cooked chicken assembled cold") and keeps every safety number in cited, checkable rules. That is the right shape for letting an assistant help with food safety without letting it decide food safety.

A nice touch worth keeping is the mitigation hint. When a conservative rule caps a food low because a safer process isn't documented — cooked rice capped at 2 days because rapid chilling wasn't recorded — the advisor can surface "record rapidly chilled to extend this from 2 to 4 days," but only when ticking that box would actually change the answer. And when evidence is thin, a component is submerged in oil, or a mitigation is available, the result is flagged as needs review so a human confirms it. Nothing dangerous is silently auto-applied.

5.2 The rulebook is data, and it varies by region

The food-safety rules are versioned, shared data, keyed by jurisdiction and storage condition — one region's food code differs from another's. The rulebook is global: every business reads the same jurisdiction's table. Tuning a single day-cap is a data edit, and supporting a second region is a second data set, not a reworking of the logic — the same region-by-region stance the system takes for tax.

5.3 Shelf-life depends on how it is stored

The same food keeps for very different lengths of time frozen, chilled, or at room temperature, and the safe-keeping advice reflects that: both the classification evidence and the rulebook key on the storage condition — frozen, chilled, or ambient — so the advisor can answer "how long does this keep?" with a frozen answer and a chilled answer. The storage condition is one of those three plain values; it carries no temperature band of its own.

The item itself carries a single shelf-life figure, taken at its default storage condition — the way the operation actually keeps it (milk: chilled; ice cream: frozen). That default does more than label the item: it is the condition whose shelf-life informs the received food's use-by date (§6).

5.4 A produced item cannot outlive its shortest-lived component

This is the most-restrictive-component rule from food-safety practice: a thing you make is only safe as long as its fastest-spoiling ingredient. So a produced item's shelf-life cap is the minimum of the food-attribute build's components' remaining lives — computed on the food-attribute rollup (§2), alongside nutrition and allergens.

Worked example. A grain bowl uses a cooked-chicken prep item good for 3 days and a dressing prep item good for 7. The bowl's cap is min(3, 7) = 3 days. If the chicken's recipe later changes and its cap drops to 2 days, the next walk drops the bowl to 2 — driven by the same single refresh that updates nutrition.

5.5 The shelf-life is recorded, with a version stamp — and the field model is honest

The computed shelf-life cap of a made item — the most-restrictive-component ceiling from §5.4 — is stored alongside the nutrition and allergen profiles it was rolled up with. It is always recipe-derived, stamped with the recipe version it was computed from and the culprit component that set the ceiling. (That version stamp, not an advisor-vs-by-hand marker, is what lets the single refresh-signal mark the cap stale when the recipe changes — the by-hand-vs-computed provenance envelope belongs to the nutrition and allergen profiles, §3.5, not to the cap.) The item's own use-by figure is a plain stored value the operator sets or the advisor suggests.

The cap advises; it does not overwrite or block. If a hand-typed use-by runs longer than the computed cap — a careless 60-day entry over a 3-day ceiling — the food-facts read raises a warning that the value exceeds the safe cap, naming the component responsible. The operator's number is left as entered; nothing is silently clamped, and there is no gate demanding a reason to exceed the maximum. The warning is the safeguard: the dangerous gap is surfaced, never hidden.

The field model itself separates two genuinely different ideas:

  • Use-by — the safety date. Hard: past it, the food is unsafe. This is the shelf-life figure that informs the received food's use-by date (§6).
  • An open-vs-sealed adjustment — "once opened, keep for N days" — because a sealed pack and an opened one keep for different lengths of time. Note its advisory nature: this is an item-level fact that informs guidance and labels (the day-dot a cook writes when opening the pack). Once-opened life is applied through physical labeling and handling.

And shelf-life is ungated: any stocked perishable has one. Raw chicken has a shelf-life as surely as a prepped sauce does. The advisory recalculation simply helps most for produced items, where the recipe is the only way to know.

Worked example (why this exists). Real stored data once carried 60-day shelf-lives on raw marinated chicken. Classified, that food is raw red meat that has been marinated raw — which the rulebook maps to 2–3 days, cited to government marinating guidance — and flagged needs review so a human confirms. The number a person typed in was a food-safety hazard; recomputing it from how the food is actually made is the entire reason this advisory exists.


6. Expiry and waste

A received food's use-by date follows the receiving facts taught in Procurement.

When food has expired, the operator records the quantity actually discarded as an ordinary waste write-off with reason expired. The warehouse's weighted-average price values that loss. For example, discarding 2 liters of expired milk at €1.20/liter records €2.40 of waste.


See also

  • Nutrition math — the cooking arithmetic in full: dilution, evaporation, drained and absorbed mass, a worked batch end to end.
  • Recipes & production — the recipe explosion this layer rides, and the prep-item output weight reused as nutrition's per-100g denominator.
  • Costing & valuation — the sibling cost calculation that shares recipe yield facts, and the average cost an expiry write-off is valued at.
  • Stock ledger — the spoilage write-off.
  • Item model & policies — why nutrition, allergens, and shelf-life are available for every item.
  • Financial boundary — the conceptual accounts the stock domain exports to the operator's accounting system, including the cost-of-goods bucket an expiry write-off lands in.
  • Catalog — where an item's own and recipe-derived nutrition and allergen facts meet the rest of the catalog.
  • Glossary — food attribute, nutrition profile, two-axis yield, allergen, shelf-life, food-safety policy table, use-by date, expiry write-off.
Last updated · History