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 dish'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 the single place where shelf-life touches the stock books — a batch gets an expiry, the soonest-to-expire is used first, and anything that lapses is written off.
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. A dish'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. They cross into the ledger at exactly one seam: a batch's expiry date, derived from its shelf-life, which decides the order stock is used in and triggers a write-off when it lapses (§6).
Everything above that seam — the nutrition figures, the allergen list, the safe-keeping window, and the trip down the recipe that computes them — is product information layered on the catalog. Everything at the seam is ordinary ledger machinery: a batch with an expiry date, a use-it-first consumption order, and a spoilage write-off. The rest of this chapter is the layer above; §6 is the seam.
2. The shared recipe tree — one model, aligned refreshes¶
A restaurant's recipes form a tree. A dish is made of ingredients; 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 dish costs, you walk that tree from the dish 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, it never loops forever on a recipe that accidentally contains itself, and it remembers intermediate results so a shared sub-recipe isn't recomputed for every dish 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 |
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.
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 stocked 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 one basis¶
An item or prep-item profile is stated per 100 grams — the operational basis recipes consume. A dish profile is stated per serving, because that is the storefront and point-of-sale fact operators need ("612 kcal a serving"), and a recipe-less dish may not have a reliable serving weight. Per-100g dish declarations for a regional label are derived at render time only when a serving weight exists; there is no second stored profile to keep in step.
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. Sodium in particular is non-negotiable in food service. (Public food databases supply all nine of these; an earlier approach imported them and then quietly dropped five — storing and indexing fiber, sugars, sodium, saturated fat, and cholesterol only to throw them away. The model keeps the whole set the source data already supports.)
A prep item counted by the piece or by volume can still carry a nutrition profile — it just needs its batch's output weight declared, which the kitchen does by weighing one batch once. With that weight the prep earns its per-100-gram figures and its grams-per-piece, so any dish built on it computes in full. Without it the prep is weightless, and there is nothing to convert its pieces into the grams the math needs.
Computed nutrition is all-or-nothing. A dish or prep gets computed figures only when every ingredient it draws is fully profiled — each leaf carrying all nine nutrients, and every prep along the way weighable. The moment one branch can't be accounted for — a leaf with no nutrition profile, a label that simply omits one nutrient, a weightless prep, a dish whose serving count is unknown — the whole panel goes blank. There is no confident partial: "Calories 540, Protein 32 g, Cholesterol —" reads like a real declaration with one gap, when the honest truth is "this dish's nutrition can't be stated yet." So instead of a half-filled panel, the food shows no computed figures and the short list of what to complete — the ingredients still missing a profile, a weight, or a serving count, named so the operator can fix them. A subject built from a recipe but with no recipe at all simply asks for one ("add a recipe to compute nutrition"). The bar is deliberately strict: a single ingredient missing a single nutrient holds the dish back until it's completed, because a nutrition label is a statement a guest relies on, and a wrong- looking-but-confident one is worse than an honest blank.
Declaring nutrition is optional throughout; a kitchen that doesn't publish figures simply never completes those profiles, and nothing else waits on it. Dishes are never weightless this way, because a dish's per-serving figures divide batch nutrients by serving count, not by batch grams — but a dish still needs its serving count known, and every ingredient under it fully profiled, before it states a number. A manual figure an operator types in is taken as a complete statement on its own, so a hand-pinned profile always shows even while its computed alternative is still waiting on a missing ingredient.
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 a prep item or a dish — 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 dish 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.
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 the dish'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 prep item and dish inherits the union of the allergens of everything it contains. If any component anywhere beneath a dish carries an allergen, the dish 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 dish?" 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 dish served across a counter, not just a packaged product, so a restaurant must be able to answer "does this contain celery?" for every dish 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 dish 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 dish; 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 a dish'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 guest (the recipe has it, the label doesn't) must never be silent.
Computing the dish 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 dish 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 dish 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-dish 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 stamps a batch's expiry date when stock arrives (§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 its 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 the item actually stores and the one a batch's expiry is stamped from (§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). The ledger does not track which physical pack is open — nobody registers openings (see the FEFO principle below) — so a lot's recorded use-by date is not re-stamped by an opening event; enforcing once-opened life is kitchen labeling practice, backed by counts.
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. The one stock seam: a batch's expiry → use-it-first → write-off¶
Shelf-life touches the stock books in exactly one place, and at that seam it is ordinary ledger machinery — no special food-safety primitives. The mechanics live in Stock ledger; here is the domain logic.
First, the unit involved. A lot is a tracked batch of an item — a specific delivery of milk, a specific production run of sauce — kept identifiable so it can be traced and expired. Only items the operator chooses to track this way get lots; for fungible staples, the books just track a total quantity and an average cost, with no per-batch detail.
- A lot gets an expiry. When tracked stock is received (or produced), the new lot is stamped with an expiry date = the receipt or production date plus the item's shelf-life for its default storage condition (§5.3) — or a date read off the label or set by the operator. The hard date here is the use-by. The expiry rides with the lot; the value and on-hand math stay batch-agnostic.
- Soonest-to-expire is used first. When tracked stock is consumed, the default is to draw down the earliest-expiring lot first. The canonical name for this is FEFO — first-expiry-first-out. It supersedes the simpler "oldest-received first" rule, and it is why fungible food doesn't need separate per-batch cost layers — the expiry date, not a cost layer, decides the order.
- The book follows the rotation — nobody scans a package. Lot draw-down is an inference, not a data-entry step. When consumption posts, the books assume the earliest-expiring lot was the one used, because that is exactly what a well-run kitchen's physical rotation does anyway — new stock behind old, front package first. No one registers "I opened this pack"; an operator names a specific lot only when physically holding it and it matters — writing off an expired batch (the lot number is the supplier's batch number off the label; for house-made preps the production date is the lot identity, the same date a day-dot label carries) or assigning lots during a count. The per-lot picture is therefore honest best-effort bookkeeping: its job is "what should be on hand and what expires Tuesday" — expiry alerts and pre-filled write-offs. The stock count trues its quantities like every other book figure (see Stock ledger); its identity no count can see. When the kitchen's real rotation departs from FEFO — the newer bag grabbed first — the item's total still matches the book, nothing posts, and the books hold the right quantity under the wrong batch label. That error is self-limiting: it exists only while the lots overlap and expires with them, and it can mislead only the advisory figures — an expiry alert naming the wrong batch, a trace answered approximately — never quantity or money. The corrective is on the floor, not at the keyboard: rotation discipline, plus naming the actual lot when one is physically in hand, as an expired batch's write-off does.
- A lapsed lot is written off. A lot that passes its expiry leaves stock the same way any spoilage does — an ordinary write-off out of inventory, tagged with the reason expired. There is no special "expiry" document; an expired lot is just spoilage with a specific reason. Surfacing a "use these soon" list before the fact is an advisory finding and a close-time report, not a ledger entity.
Worked example. Milk's default storage condition is chilled, with a 7-day shelf-life, so a tracked lot received on the 1st is stamped to expire on the 8th. A draw on the 5th issues from this lot first (FEFO). Whatever is left on the 9th is past its use-by and is written off — an ordinary spoilage write-off, reason expired, valued at the warehouse's weighted-average cost (a lot identifies and traces a batch; it carries no cost of its own) — so the loss lands in cost of goods sold like any other waste, one of the figures the stock domain exports to the operator's accounting system (see Financial boundary).
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 move-line layer where a lot's expiry lives, the FEFO issue order, and the spoilage write-off the expiry triggers.
- Item model & policies — which items get a tracked lot (and thus an expiry), and why nutrition, allergens, and shelf-life are ungated.
- 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.
- Menu & catalog — where an item's nutrition profile and a dish's nutrition and allergen facts surface in the catalog.
- Glossary — food attribute, nutrition profile, two-axis yield, allergen, shelf-life, food-safety policy table, FEFO, lot expiry, expiry write-off.