VitaScore GFM
The VitaScore Gap Filling Module calculates human health impacts of food products based on dietary risk factors from the Global Burden of Disease (GBD) study. It converts food composition into Disability-Adjusted Life Years (DALYs), enabling health impact comparisons alongside environmental metrics.
Quick Reference
| Property | Description |
|---|---|
| Runs on | FoodProductFlowNode |
| Dependencies | AggregationGapFillingWorker, DailyFoodUnitGapFillingWorker |
| Key Input | Food category amounts, nutrient values, daily food unit |
| Output | VitaScore in DALYs per dietary risk factor |
| Trigger | Product has aggregated food categories and daily food unit |
When It Runs
The module triggers when:
- Aggregation of food categories is complete
- Daily Food Unit (DFU) calculation is complete
- The node has valid
amount_per_category_in_flowdata - The node has a valid
daily_food_unitvalue
Key Output
The module produces:
- Total VitaScore: Aggregated health impact in DALYs
- Per-factor scores: Individual DALY contributions from each dietary risk factor
- Both current and legacy versions: Two calculation methods for comparison
Scientific Methodology
VitaScore quantifies the health impact of food consumption using Disability-Adjusted Life Years (DALYs) based on the Global Burden of Disease study. One DALY represents the loss of one year of full health.
Global Burden of Disease Framework
The GBD study identifies dietary risk factors that contribute to disease burden. The VitaScore uses 13 risk factors grouped into two categories:
Positive Risk Factors (higher consumption is beneficial):
- Diet low in fruits
- Diet low in vegetables
- Diet low in whole grains
- Diet low in nuts and seeds
- Diet low in milk
Negative Risk Factors (higher consumption is harmful):
- Diet high in sodium
- Diet high in processed meat
- Diet high in red meat
- Diet high in energy
- Diet high in fat
- Diet low in fat
- Diet high in protein
- Diet low in protein
Theoretical Minimum Risk Exposure Level (TMREL)
Each dietary risk factor has an optimal consumption range called the TMREL - the exposure level that minimizes health risk. The VitaScore calculates how far actual consumption deviates from this optimal range.
The TMREL is defined by two bounds:
- t_1: Lower bound of optimal range
- t_2: Upper bound of optimal range
The VitaScore Formula
The core formula calculates a risk fraction (f_r) for each dietary risk factor, then multiplies by the associated DALY burden:
For positive factors (where consuming more is better):
f_r = (t_2 / (t_2 - t_1)) - x_r / (scaling * (t_2 - t_1))
For negative factors (where consuming less is better):
f_r = x_r / (scaling * (t_2 - t_1)) - (t_1 / (t_2 - t_1))
Where:
- x_r: Amount of the risk factor in the food product
- t_1, t_2: TMREL lower and upper bounds
- scaling: Normalization factor (see below)
- f_r: Risk fraction (clamped to 0-1 range)
Final calculation:
VitaScore[r] = DALY_r * f_r
Total VitaScore = sum(VitaScore[r] for all risk factors)
Normalization Methods
The VitaScore supports three normalization methods for different risk factors:
| Method | Description | Usage |
|---|---|---|
| per_dfu | Scale by Daily Food Unit | Most risk factors (fruits, vegetables, meat, etc.) |
| per_total_energy | Scale by total energy content | Fat and protein as percentage of calories |
| per_self | No scaling (factor = 1.0) | Direct measurements |
Daily Food Unit (DFU)
The Daily Food Unit represents the typical daily consumption amount of a food category. It normalizes the calculation so that a food's health impact is proportional to how much of the daily diet it represents.
Implementation Details
Covered Dietary Risk Factors
The module calculates health impacts for 13 dietary risk factors. Each factor has a Theoretical Minimum Risk Exposure Level (TMREL) defining the optimal consumption range.
Positive Risk Factors (Higher Consumption is Beneficial)
| Risk Factor | TMREL Lower | TMREL Upper | Unit | Normalization | DALY | Dietary Guideline Reference |
|---|---|---|---|---|---|---|
| Diet low in fruits | 200 | 300 | g/day | per_dfu | 61.24 | WHO recommends ≥400 g/day fruits and vegetables combined1 |
| Diet low in vegetables | 340 | 500 | g/day | per_dfu | 30.57 | WHO recommends ≥400 g/day fruits and vegetables combined1 |
| Diet low in whole grains | 100 | 150 | g/day | per_dfu | 82.86 | US guidelines: 3-4 servings/day (~48-64 g)2 |
| Diet low in nuts and seeds | 16 | 25 | g/day | per_dfu | 13.98 | EAT-Lancet: ~50 g/day nuts3 |
| Diet low in milk | 350 | 520 | g/day | per_dfu | 9.42 | 2-3 servings dairy/day (~400-600 ml)4 |
Negative Risk Factors (Higher Consumption is Harmful)
| Risk Factor | TMREL Lower | TMREL Upper | Unit | Normalization | DALY | Dietary Guideline Reference |
|---|---|---|---|---|---|---|
| Diet high in sodium (salt) | 1 | 5 | g/day | per_dfu | 29.43 | WHO recommends <5 g salt/day (<2 g sodium)5 |
| Diet high in processed meat | 0 | 4 | g/day | per_dfu | 96.94 | EAT-Lancet: avoid or minimize processed meat3 |
| Diet high in red meat | 18 | 27 | g/day | per_dfu | 74.25 | EAT-Lancet: ~14 g/day (~100 g/week)3 |
Macronutrient Risk Factors (Energy and Percentage of Total Energy)
| Risk Factor | TMREL Lower | TMREL Upper | Unit | Normalization | DALY | Dietary Guideline Reference |
|---|---|---|---|---|---|---|
| Diet high in energy | 650 | 1250 | kcal | per_self | 88.66 | A balanced meal: 450-850 kcal (~⅓ of 2200 kcal/day)4 |
| Diet high in fat | 27.5% | 50% | % of kcal | per_total_energy | 88.66 | EFSA/US guidelines: 20-35% of energy from fat46 |
| Diet low in fat | 5% | 27.5% | % of kcal | per_total_energy | 88.66 | EFSA/US guidelines: 20-35% of energy from fat46 |
| Diet high in protein | 22.5% | 60% | % of kcal | per_total_energy | 88.66 | US AMDR: 10-35% of energy from protein6 |
| Diet low in protein | 0% | 22.5% | % of kcal | per_total_energy | 88.66 | US AMDR: 10-35% of energy from protein6 |
DALY values are expressed as DALYs per 100,000 person-years and represent the disease burden attributable to each dietary risk factor.
Sodium to Salt Conversion
Since dietary guidelines often refer to salt (sodium chloride) rather than sodium, the module converts sodium values:
# Sodium is approximately 39.3% of salt by mass
NA_PERCENT_IN_NACL = 39.337 # %
# Convert sodium to salt equivalent
salt_quantity = sodium_quantity * (100 / NA_PERCENT_IN_NACL)
Macronutrient Energy Conversion
Fat and protein are converted from mass to energy for percentage-of-calories calculations:
FAT_CALORIES_PER_GRAM = 9.0 # kcal/g
PROTEIN_CALORIES_PER_GRAM = 4.0 # kcal/g
fat_kcal = fat_grams * FAT_CALORIES_PER_GRAM
protein_kcal = protein_grams * PROTEIN_CALORIES_PER_GRAM
Version Support
The module maintains two calculation versions:
| Version | Description |
|---|---|
| current | Latest GBD 2021 DALY values |
| legacy | Original DALY values for backward compatibility |
Both versions are calculated and stored, allowing comparison of results across methodology updates.
Full Code Reference
Main Calculation Flow
async def run(self, calc_graph: CalcGraph) -> None:
"""Perform vitascore calculation."""
# Get aggregated food categories and daily food unit
amount_per_category_in_flow_prop = self.node.amount_per_category_in_flow
dfu_prop = self.node.daily_food_unit
# Add salt and nutrition-derived risk factors
salt_qty, energy_qty, fat_qty, protein_qty = (
self.retrieve_salt_and_nutrition_amounts_for_activity_production_amount()
)
# Compute vitascore for both versions
for version in (VitascoreVersionEnum.legacy, VitascoreVersionEnum.current):
vitascore_for_local = self.compute_vitascore(
amount_per_category_in_flow_prop,
dfu_prop,
version,
ReferenceAmountEnum.amount_for_activity_production_amount,
)
# ... store results
Core VitaScore Formula Implementation
def vitascore_formula(
self,
food_category: QuantityPackageProp,
dfu: float,
legacy_or_current: str,
for_reference_amount: ReferenceAmountEnum,
) -> dict[UuidStr, ReferencelessQuantityProp]:
"""Formula for computing vitascore."""
vitascore = {
self.gfm_factory.vitascore_term.uid: ReferencelessQuantityProp(
value=0.0, unit_term_uid=self.gfm_factory.daly_unit_term.uid
)
}
for idx, term in enumerate(
self.gfm_factory.positive_terms + self.gfm_factory.negative_terms
):
if term.xid in self.gfm_factory.used_categories_xid:
# Get TMREL bounds from term data
t_1 = float(term.data.get("tmrel").get("lower"))
t_2 = float(term.data.get("tmrel").get("upper"))
normalization = TmrelNormalizationEnum(
term.data.get("tmrel").get("tmrel_normalization")
)
# Get amount from food categories
if term.uid in amount_per_category_in_flow_present:
x_r = float(amount_per_category_in_flow_present[term.uid].value)
else:
x_r = 0.0
# Get DALY burden for this risk factor
daly_r = float(term.data.get(legacy_or_current).get("amount"))
# Determine scaling factor
if normalization == TmrelNormalizationEnum.per_dfu:
scaling = dfu
elif normalization == TmrelNormalizationEnum.per_total_energy:
scaling = energy_kcal_value
elif normalization == TmrelNormalizationEnum.per_self:
scaling = 1.0
# Calculate risk fraction
if scaling <= 0.0:
f_r = 0.0
else:
if idx < len(self.gfm_factory.positive_terms):
# Positive factors: more is better
f_r = (t_2 / (t_2 - t_1)) - x_r / (scaling * (t_2 - t_1))
else:
# Negative factors: less is better
f_r = x_r / (scaling * (t_2 - t_1)) - (t_1 / (t_2 - t_1))
# Clamp risk fraction to [0, 1]
f_r = max(0, min(1, f_r))
# Calculate and accumulate vitascore
vitascore[term.uid] = ReferencelessQuantityProp(
value=daly_r * f_r,
unit_term_uid=self.gfm_factory.daly_unit_term.uid
)
vitascore[self.gfm_factory.vitascore_term.uid].value += daly_r * f_r
return vitascore
Salt and Nutrition Retrieval
def retrieve_salt_and_nutrition_amounts_for_activity_production_amount(
self,
) -> Tuple[Optional[ReferencelessQuantityProp], ...]:
"""Extract salt and macronutrient quantities from nutrition data."""
# Check aggregated nutrients first, then nutrient values
if aggregated_nutrients := self.node.aggregated_nutrients:
nutrition_pkg = aggregated_nutrients.amount_for_activity_production_amount()
elif nutrient_values := self.node.nutrient_values:
nutrition_pkg = nutrient_values.amount_for_activity_production_amount()
# Handle sodium chloride splitting
if nutrition_pkg and (sodium_chloride_term.uid in nutrition_pkg.quantities):
nutrition_pkg = split_sodium_chloride(nutrition_pkg)
# Convert sodium to salt
if sodium_term.uid in nutrition_pkg.quantities:
salt_qty = nutrition_pkg.quantities[sodium_term.uid].duplicate()
salt_qty.value *= 100 / NA_PERCENT_IN_NACL
# Get energy and convert fat/protein to kcal
energy_kcal_qty = nutrition_pkg.quantities.get(energy_term.uid)
if fat_mass_qty := nutrition_pkg.quantities.get(fat_term.uid):
fat_in_grams = convert_to_grams(fat_mass_qty)
fat_qty = ReferencelessQuantityProp(
value=fat_in_grams * FAT_CALORIES_PER_GRAM,
unit_term_uid=kcal_term.uid
)
if protein_mass_qty := nutrition_pkg.quantities.get(protein_term.uid):
protein_in_grams = convert_to_grams(protein_mass_qty)
protein_qty = ReferencelessQuantityProp(
value=protein_in_grams * PROTEIN_CALORIES_PER_GRAM,
unit_term_uid=kcal_term.uid
)
return salt_qty, energy_kcal_qty, fat_qty, protein_qty
Calculation Example
Scenario: Calculate VitaScore for 100g of processed ham
Step 1: Identify Risk Factor Amounts
From food category aggregation:
- Processed meat: 100g
- Red meat: 0g (counted separately from processed)
- Fruits: 0g
- Vegetables: 0g
From nutrient values:
- Sodium: 1200mg (converts to 3050mg salt)
- Energy: 145 kcal
- Fat: 8g (72 kcal)
- Protein: 18g (72 kcal)
Step 2: Calculate Risk Fractions
Diet high in processed meat (negative factor):
- TMREL: 0-0g per day (optimal is zero consumption)
- Scaling: Daily Food Unit (assume 2000g daily food intake)
- Normalized amount: 100g / 2000g = 0.05 (5% of daily intake)
- Risk fraction: Calculated based on TMREL deviation
Diet high in sodium:
- TMREL: 1000-5000mg per day
- Salt amount: 3050mg
- Risk fraction: Based on where 3050mg falls in TMREL range
Diet low in fruits (positive factor):
- Amount: 0g
- Risk fraction: Maximum (no fruit content)
Step 3: Apply DALY Values
Each risk fraction is multiplied by the associated DALY burden from GBD data:
- Processed meat DALY burden: ~40 DALYs per 100,000 person-years
- Sodium DALY burden: ~30 DALYs per 100,000 person-years
Step 4: Sum Total VitaScore
Total VitaScore = sum of (DALY_r * f_r) for all 13 risk factors
The result is expressed in micro-DALYs per serving, representing the proportional health burden.
Data Sources
Global Burden of Disease Study
The VitaScore is based on the GBD 2021 study:
GBD 2021 Risk Factors Collaborators (2024). Global burden and strength of evidence for 88 risk factors in 204 countries and 811 subnational locations, 1990-2021: a systematic analysis for the Global Burden of Disease Study 2021. The Lancet.
Key data extracted:
- DALY burdens for each dietary risk factor
- TMREL ranges for optimal consumption
- Relative risk estimates
TMREL Values
The Theoretical Minimum Risk Exposure Levels are sourced from:
- GBD 2021 Appendix 1: Detailed TMREL specifications for each risk factor
- Lancet Supplementary Materials: Supporting data for TMREL derivation
Mediation Factors
The GBD uses mediation factors to account for overlapping risk pathways. For example, red meat impacts health both directly and through its effect on body mass index (BMI).
Known Limitations
Methodological Constraints
- Population averages: DALY values represent global or regional averages, not individual risk
- Age/gender specificity: Current implementation uses population-wide values (age/gender-specific values available but not implemented)
- Risk factor overlap: Mediation factors partially but not fully address correlated risks
Data Limitations
- Food matching: Requires accurate food category classification
- Nutrient completeness: Sodium/fat/protein calculations depend on complete nutrition data
- TMREL uncertainty: Optimal consumption ranges have inherent scientific uncertainty
Coverage Gaps
- Country specificity: DALY values can be made country-specific but currently use global estimates
- Processed foods: Complex processed foods may have incomplete category mapping
- Novel foods: New food categories may lack established risk factor associations
References
-
GBD 2021 Risk Factors Collaborators (2024). Global burden and strength of evidence for 88 risk factors in 204 countries and 811 subnational locations, 1990-2021. The Lancet, 403(10440), 2162-2203.
-
Eaternity (2017). VitaScore Documentation. https://eaternity.org/assets/smart-chefs/2017-12-12_VitaScore_Documentation_web.pdf
-
Ernstoff, A., et al. (2020). Towards Win-Win Policies for Healthy and Sustainable Diets. Nutrients, 12(9), 2745.
-
Murray, C.J.L., et al. (2020). Global burden of 87 risk factors in 204 countries and territories, 1990-2019: a systematic analysis for the Global Burden of Disease Study 2019. The Lancet, 396(10258), 1223-1249.
-
GBD Results Tool. Institute for Health Metrics and Evaluation. https://vizhub.healthdata.org/