SASEL Lab
SASEL Lab
SASEL Lab
An environmental nutrition decision system that scores the same food or meal for nutritional quality, health impact and environmental footprint, on one evidence base.
EcoDish365 answers three questions about the same plate of food at once: how nourishing it is, what it does to long-term health, and what it costs the environment to produce. Each of those questions has a mature scientific literature and an established scoring method behind it. EcoDish365 implements those methods faithfully, runs them over a shared food database, and reports the answers side by side.
The platform serves individuals choosing what to eat, researchers studying where nutrition and sustainability meet, and policymakers who need defensible evidence behind a dietary or procurement decision.
A single food or meal is scored through four independent systems. The Health Star Rating gives the front-of-pack style summary used in Australia and New Zealand. The Food Compass Score assesses nine domains across dozens of attributes. The Healthy Eating Food Index measures alignment with Canadian dietary guidance. The Health and Nutritional Impact method translates dietary risk factors into disability-adjusted life years, so a food can be expressed as minutes of healthy life gained or lost. Alongside these runs a full life cycle assessment of the same item.
Each system carries its own subtleties, and the implementation honours them. The health impact model applies the double-counting carve-outs published in the supplementary material of the source paper, so that calcium is suppressed for foods already counted as milk, and dietary fibre is routed differently depending on whether it came from fruit, vegetables, legumes and whole grains or from elsewhere. Plant beverages are held out of the dairy milk risk factor, and hundred percent juice is held out of the sugar-sweetened beverage category, both following the Global Burden of Disease definitions.
Environmental data exists for agricultural commodities and generic food products. A user asks about "low-fat chocolate milk" or "homemade beef stew". Bridging that gap is the central engineering problem, and EcoDish365 solves it with a three-stage resolution chain.
A food description is first embedded and compared against the Agribalyse 3.2 inventory catalogue, and the twenty closest candidates go to a language model that ranks them and returns a code, a confidence and a written justification. The model may only choose from the candidates it was shown, and any answer outside that set is rejected. Where a composite dish resists direct matching, a second stage asks the model to express it as a mass-weighted ingredient list drawn from the same constrained vocabulary; each ingredient is then matched on its own and the impacts are recombined by mass. Ingredient masses that fail to sum to the stated recipe mass within five grams are rejected outright. Where confidence stays low, the chain falls back to published group means, and the result records that it did.
This architecture is built on cited precedent, including a published measurement that a bare language model inventing non-existent inventory labels roughly one time in five, which is precisely what the constrained-candidate rule prevents. The whole matching layer is optional: with no model available the platform runs on group means alone and says so.
Impacts are converted into Canadian dollars using published environmental prices, rebased to the same year as the Canadian social cost of carbon, with regional multipliers that are individually argued in the source. Canadian climate damage carries a premium for Arctic amplification; Canadian water scarcity carries a discount, since the country holds roughly seven percent of global renewable freshwater for half a percent of the world population. Any country without a published valuation study collapses to the unadjusted prices, with a clear note that it has done so.
The platform also proposes changes. Given a recipe, the substitution engine searches for ingredient swaps and returns a Pareto frontier of options that trade nutrition, environmental impact and culinary plausibility against each other, screened by quality gates so the suggestions remain cookable.
EcoDish365 was developed at the SASEL Lab, McGill University, and is available on the web.
Nutrient data comes from the 2026 release of the Canadian Nutrient File, 5,691 foods with their full nutrient amounts, food groups and measure conversions. That is joined by United States sources, the Food Data Central foundation and legacy tables, the food surveys database and its associated food pattern equivalents, by the French CIQUAL composition table, and by the West African Food Composition Table, which extends the platform beyond North American diets. Environmental inventory data comes from Agribalyse 3.2, and characterisation factors from the official ReCiPe 2016 workbooks, extracted by a reproducible pipeline that records a SHA-256 hash and an extraction timestamp for every pack.
The assessment pipeline is written to be independent of any single methodology, perspective or country. Factor packs are loaded at runtime, so the method can be replaced. All three ReCiPe cultural perspectives are selectable, covering short, medium and long time horizons for damage. A country may be supplied as an ISO-3 code, and where the source workbook publishes country-specific midpoint-to-endpoint factors, those are substituted for the world average; the current release covers the water consumption pathways this way. A user may also choose whether to view impacts from a global supply-chain position or an in-country position.
The current release consumes three midpoint categories, global warming in carbon dioxide equivalent, land use in square metre crop equivalent years, and water consumption. Each is carried through to endpoint damage and normalised against per-person figures. Every category carries an uncertainty band and a confidence rating traced to its source document, with the toxicity pathways explicitly flagged as less robust in line with the caveats the source authors themselves publish. A per-category audit records which factor source produced each number, and that audit is exposed through the API.
The matcher embeds a food description and retrieves the twenty nearest inventory entries by cosine similarity, then asks a language model at temperature zero for a strict JSON ranking. Seven gates stand between that call and a returned result, covering normalisation, caching, retrieval, model availability, the constrained-candidate check, a confidence threshold, and the fallback path. The same seven-gate pattern is reused for free-text search against the Canadian Nutrient File, so a user typing "aubergine" reaches the eggplant entry with a recorded confidence and a justification.
Composite foods get an additional stage. When a dish belongs to a food group dominated by prepared items, mixed dishes, soups, fast foods, baby foods, sausages, sweets, snacks and baked products, and direct matching returns low confidence, the decomposer requests a mass-weighted ingredient breakdown restricted to resolvable inventory entries. Every ingredient is matched through the standard path and the dish impact is the mass-weighted sum. Decompositions are rejected when a code falls outside the retrieved set, when ingredient masses miss the stated total by more than five grams, or when the reported confidence falls below threshold.
The four scoring systems share a food access layer and each carries its own integrator. Processing level is assigned by a staged NOVA classifier built on the Monteiro four-group framework, which begins with hard rules for food groups that the framework assigns categorically, then applies word-boundary lexical rules covering ingredient isolates, emulsifiers and stabilisers, and industrial processes such as extrusion and hydrolysation. That classifier was written to replace a substring approach whose failure modes are documented in the source, among them a rule that read the word "oil" inside the word "boiled".
The health impact model extracts risk factors per ingredient, applies the published carve-outs that prevent a single benefit being counted twice, and passes the aggregate to a compute engine that returns the burden in disability-adjusted life years.
Midpoint impacts are priced from the CE Delft environmental prices handbook, published in 2015 euros per kilogram and converted into 2021 Canadian dollars so the base year matches the Canadian social cost of carbon. Canadian regional multipliers are applied when a caller asks for Canada, and each multiplier is justified in the source against national inventory reporting and national statistics. Countries with no published valuation study receive the unadjusted prices together with an explicit log message explaining the Canadian calibration.
The substitution engine composes several passes: candidate discovery for each ingredient, hard constraint parsing for dietary and allergen rules, a culinary plausibility check that rejects swaps a cook would refuse, a quality gate that catches extreme nutrient swings, a ranking bonus for swaps that fill food pattern gaps, and a sustainability proxy score. Surviving candidates are combined and reduced to a Pareto frontier, so the user receives the set of swaps where no option is beaten on every axis at once.
The four scoring engines are implemented in Rust and exposed to Python as a compiled extension module, built with link-time optimisation and a single codegen unit. Parity tests hold the Rust thresholds to the Python reference. The Django backend exposes roughly 167 routes and the Next.js frontend presents 56 pages, covering food search and comparison, each scoring system, environmental calculation and comparison, dietary pattern analysis, meal building and documentation. The test suite carries 324 test functions across 32 files.
Nutrition Facts panels can be read from a photograph. An uploaded image is normalised to a common format and size and hashed, the hash keys a cache along with the prompt and schema versions, a multimodal model returns structured JSON, that JSON is validated against a schema so failures surface as structured reasons, and sanity-range guards reject physically implausible readings such as five grams of sodium in a serving. Fields that fail a guard have their confidence set to zero and a warning attached, and only a total failure marks the extraction unsuccessful.
Tech Stack
Categories