Reviewed-on: #14 Co-authored-by: Nicholas Ward <nicholaspward@outlook.com>
144 lines
7.3 KiB
Markdown
144 lines
7.3 KiB
Markdown
# Culinary data model
|
||
|
||
## Direction
|
||
|
||
Schema version 2 is a vendor-neutral culinary domain model designed to map
|
||
cleanly to relational tables while remaining portable, reviewable YAML. Astro is
|
||
a presentation consumer only. It must not become the authority for quantities,
|
||
conversions, nutrition, allergens, purchasing, or calculated values.
|
||
|
||
`schema_version` describes a document's data contract. Recipes represent their
|
||
current canonical state. Names used for search or display belong in ingredient
|
||
`aliases`; canonical references always use stable entity IDs.
|
||
|
||
## Canonical and derived boundaries
|
||
|
||
Canonical writable records live in SQLite. They include authored or observed
|
||
facts: recipes, ingredients, measurements, provenance, suppliers, packages,
|
||
and price observations. YAML under `culinary/` is portable seed/interchange
|
||
data, while generated site projections and exports are downstream products.
|
||
Neither is an independently writable source of truth.
|
||
|
||
Derived recipe records contain reproducible nutrition, allergen rollups, and
|
||
costs. They identify the recipe, calculation version, calculation time, and an
|
||
input fingerprint. Derived values are disposable and must never
|
||
be copied back into canonical source fields as though they were observations.
|
||
|
||
## Entity model
|
||
|
||
The schema set in `container/schemas/v2/` defines these prospective relational
|
||
entities:
|
||
|
||
| Entity | Main relationships |
|
||
| --- | --- |
|
||
| Recipe | components; ordered lines and steps; ingredients or sub-recipes |
|
||
| Ingredient | aliases; sourced density/measure observations; nutrition/allergen mappings |
|
||
| Unit | dimension and conversion to a base unit within that dimension |
|
||
| Prep action | optional default edible-yield factor |
|
||
| Source mapping | ingredient, purchasing-item, or recipe nutrition/allergen evidence |
|
||
| Supplier | purchasing items |
|
||
| Purchasing item | ingredient, supplier, package, dated prices, product mappings |
|
||
| Equipment | referenced by recipe steps |
|
||
| Collection | ordered recipe references for menus or collections |
|
||
| Derived recipe | calculated cost, nutrition, allergens, completeness, and warnings |
|
||
|
||
Recipe components, component lines, and preparation steps are embedded because
|
||
they form a recipe aggregate and are edited together. Reusable facts
|
||
remain independent records referenced by stable IDs.
|
||
|
||
Equipment is a reusable, vendor-neutral entity. A recipe may declare equipment
|
||
needed anywhere in the process with `equipment_ids`; individual steps may add
|
||
their own `equipment_ids` to show when each item is used. The displayed setup
|
||
list is the union of both sets, without duplicating equipment definitions in
|
||
recipe files.
|
||
|
||
Allergen mappings and suppliers are optional capabilities. A purchasing item
|
||
may record an exact product, package, and price without identifying a formal
|
||
supplier. Recipes and ingredients never require allergen or supplier records.
|
||
|
||
## Quantities, scaling, and yield
|
||
|
||
An `amount` is always a numeric quantity plus a normalized `unit_id`. A recipe
|
||
declares amount, percentage, or hybrid scaling. Hybrid records include both
|
||
canonical amounts and percentages; calculation tooling must verify agreement
|
||
against the declared basis.
|
||
|
||
Recipe yield is explicit and identifies whether it is measured, estimated, or
|
||
theoretical. A finished measured yield is preferred for per-100-gram nutrition
|
||
and costing. Serving count and serving size are separate optional facts.
|
||
|
||
Prep actions may override their default `yield_factor` on a recipe line. The
|
||
recipe amount is the usable quantity entering the formula; the factor determines
|
||
the larger as-purchased quantity required for costing. For example, 100 grams of
|
||
usable onion at an 80% prep yield requires purchasing 125 grams. Cooking loss
|
||
that affects the entire recipe belongs in recipe yield, not an ingredient
|
||
purchase factor.
|
||
|
||
In operational recipe management, the finished recipe yield is
|
||
authoritative for costing, portioning, and per-weight nutrition. Record the
|
||
actual finished weight after cooking and handling; this single measurement
|
||
captures evaporation, absorption, rendering, and material left on equipment.
|
||
Ingredient-specific trimming, peeling, and draining remain explicit prep-action
|
||
yield factors. Cooking water deliberately added to rice is a normal recipe input.
|
||
|
||
## Units, density, and household measures
|
||
|
||
Universal unit conversion applies only within a dimension: mass-to-mass,
|
||
volume-to-volume, time-to-time, and so on. Volume-to-mass conversion requires
|
||
ingredient-specific evidence.
|
||
|
||
For normalized units, `base_conversion` means:
|
||
`base value = source value × factor + offset`. Mass resolves to grams, volume
|
||
to milliliters, time to minutes, count to each, length to millimeters, and
|
||
temperature to Celsius. US volume units are identified explicitly; an
|
||
unqualified international "cup" is not assumed.
|
||
|
||
An ingredient density measurement stores a mass, a volume, physical state,
|
||
temperature when relevant, and provenance. This is intentionally represented
|
||
as an observed ratio rather than a context-free `density` scalar. A rounded
|
||
culinary approximation can coexist with a laboratory measurement without
|
||
pretending they have equal precision.
|
||
|
||
Ingredient `measure_conversions` cover non-density measures such as one clove,
|
||
one packed cup, or one drained can. Preparation state belongs on the conversion
|
||
because sifted, packed, chopped, and whole materials may have different bulk
|
||
measures.
|
||
|
||
## USDA FoodData Central
|
||
|
||
USDA FoodData Central mappings are provenance records, not attributes that
|
||
define an ingredient. A reviewed mapping records the FDC ID, FoodData Central
|
||
data type, exact description, retrieval date, and the normalized nutrient
|
||
values imported from that record. Candidate mappings remain distinguishable
|
||
from reviewed mappings.
|
||
|
||
Prefer Foundation Foods for suitable basic foods, followed by SR Legacy or
|
||
FNDDS when they are a better semantic match. Use a Branded record only for the
|
||
specific purchasing item or when the canonical ingredient intentionally means
|
||
that exact product. Prepared components should normally derive nutrition from
|
||
their recipes instead of being forced onto an approximate FDC record.
|
||
|
||
The USDA API key must never be stored in the repository. Future import tooling
|
||
should propose candidates, cache source payloads or normalized facts with their
|
||
retrieval dates, and require review before a mapping becomes authoritative.
|
||
|
||
## Canonical storage
|
||
|
||
SQLite is the canonical application store. The files in `culinary/` are a
|
||
portable import/export dataset and initial seed, not a second writable source of
|
||
truth. Recipes without authored instructions contain one explicit TODO step.
|
||
Formula-only conversions use a nominal 100 g basis and a
|
||
theoretical yield until those values are replaced by observed production data.
|
||
|
||
Application and agent changes must use validated domain save functions and
|
||
SQLite transactions. Direct SQL is reserved for schema-aware maintenance where
|
||
no domain operation exists. Reset/import commands flow from YAML into SQLite and
|
||
therefore overwrite the current store; export commands flow from SQLite into a
|
||
portable representation.
|
||
|
||
Astro reads SQLite through the application data layer. Generated projections
|
||
support read-only presentation, and interactive calculators are Preact islands
|
||
supplied with resolved, typed recipe data. Astro, Preact, and projections remain
|
||
presentation consumers; culinary calculations and editing originate in SQLite
|
||
and shared domain tools.
|