Files
formulation/docs/culinary-data-model.md
nicholasandnicholas 2a1e16ed30
Build & Deploy Formulation / Build & Push Image (push) Failing after 15s
Build & Deploy Formulation / deploy (push) Skipped
add core features (#14)
Reviewed-on: #14
Co-authored-by: Nicholas Ward <nicholaspward@outlook.com>
2026-08-18 18:22:34 -05:00

144 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.