Greenhouse GFM
The Greenhouse Gap Filling Module calculates greenhouse gas emissions from heated greenhouse cultivation of vegetables. Greenhouse production can increase climate impact by up to 10x compared to field-grown equivalents, making this module critical for accurate environmental assessments of fresh produce.
Quick Reference
| Property | Description |
|---|---|
| Runs on | ModeledActivityNode with FoodProductFlowNode parent containing matched vegetable terms |
| Dependencies | OriginGapFillingWorker, AttachFoodTagsGapFillingWorker, ConservationGapFillingWorker |
| Key Input | Production date, origin country, matched vegetable type |
| Output | Heating emissions, infrastructure emissions, electricity emissions |
| Trigger | Product matched to one of 7 greenhouse vegetables |
When It Runs
The module triggers when:
- A product is matched to a covered vegetable term
- The product has a valid origin country with Meteonorm climate data
- The product does NOT have an exclusion tag (frozen, canned, dried)
- Heating is required (based on climate calculations)
Key Output
The module adds three types of emissions to the calculation graph:
- Heating: Based on country climate, vegetable requirements, and production date
- Infrastructure: Glass and plastic greenhouse materials
- Electricity: Vegetable-specific energy consumption
Scientific Methodology
The greenhouse model calculates emissions using the formula:
Total Emissions = Heating + Infrastructure + Electricity
Each component is calculated per kilogram of vegetable produced.
Heating Demand Model
The heating demand model is based on Stössel et al. (2012) and uses a heat balance approach:
Q_heating = (Q_trans + Q_air) * (T_in - T_out) - Q_solar * G
Where:
- Q_trans: Heat transmission through greenhouse envelope
- Q_air: Heat loss through air exchange
- T_in: Required inside temperature for the vegetable (Kelvin)
- T_out: Monthly average outside temperature (from Meteonorm)
- Q_solar: Solar heat gain factor
- G: Monthly average solar radiation (from Meteonorm)
Heat Transmission Factor (Q_trans)
U_VALUE = 3.4 # W/m2/K - thermal insulation value
CLADDING_AREA = 54_978.2 # m2 - greenhouse envelope area
Q_TRANS_FACTOR = U_VALUE * CLADDING_AREA # = 186,925.88 W/K
The U-value of 3.4 W/m2/K represents a weighted average for conventional greenhouses according to Scharfy et al. (2017).
Air Exchange Factor (Q_air)
AIR_EXCHANGE_NUMBER = 0.24 # air changes per hour
BUILDING_VOLUME = 259_506.0 # m3
SPEC_VOLUMETRIC_ENERGY_CONSTANT_FOR_AIR = 0.32 # Wh/m3/K
Q_AIR_FACTOR = AIR_EXCHANGE_NUMBER * BUILDING_VOLUME * SPEC_VOLUMETRIC_ENERGY_CONSTANT_FOR_AIR
# = 19,924.46 W/K
Solar Heat Gain Factor (Q_solar)
Q_SOLAR_FACTOR = 0.609 * 46_800.0 * 0.99 * 0.9 * 0.7
# = 17,849.82 W/(W/m2)
Components:
- 0.609: Solar transmittance of greenhouse cover
- 46,800 m2: Ground area exposed to sun
- 0.99: Fraction of solar radiation reaching plants
- 0.9: Utilization factor
- 0.7: Conversion efficiency
Heating Emissions Calculation
Heating [kg CO2eq/kg] = Heating demand [MJ/kg] * Heating mixture [kg CO2eq/MJ]
The heating mixture depends on the country and represents the average mix of natural gas, heating oil, and renewables used in greenhouse heating.
Infrastructure Calculation
Infrastructure emissions account for the greenhouse building materials over their 30-year lifespan:
FRACTION_OF_GLASS_IN_BUILDING_MATERIAL = 0.604 # 60.4%
FRACTION_OF_PLASTIC_IN_BUILDING_MATERIAL = 0.396 # 39.6%
AREA_OF_THE_WINDOWS_EXPOSED_TO_THE_SUN = 46_800 # m2
The infrastructure mix (60.4% glass, 39.6% plastic tunnel) is based on Swiss greenhouse statistics from Scharfy et al. (2017).
Calculation:
# m2*year of greenhouse needed per kg of vegetable
building_material_per_kg = greenhouse_area / total_yield_per_year * production_amount
# Split between glass and plastic
glass_material = building_material_per_kg * 0.604
plastic_material = building_material_per_kg * 0.396
Electricity Calculation
Electricity consumption is vegetable-specific and multiplied by the country's electricity mix:
Electricity [kg CO2eq/kg] = Energy input [kWh/kg] * Electricity mix [kg CO2eq/kWh]
The electricity mix uses Ecoinvent's "market for electricity, low voltage" for each country.
Implementation Details
Covered Products
The module runs when a product is matched to one of these FoodEx2 terms:
| FoodEx2 Code | Vegetable | Model Type | Description |
|---|---|---|---|
B1458 | eggplant | eggplant | Eggplant |
A00JD | eggplant | eggplant | Aubergines |
A00JM | cucumber | cucumber | Cucumbers |
A00JR | cucumber | cucumber | Courgettes (zucchini) |
A00KY | lettuce | lettuce | Head lettuce |
A00MJ | lettuce | lettuce | Spinach |
B4946 | lettuce | lettuce | Batavia lettuce |
A00KX | lettuce | lettuce | Lettuces generic |
A1563 | lettuce | lettuce | Iceberg lettuce |
A0DLB | lettuce | lettuce | Lettuces and similar |
A1612 | lettuce | lettuce | Oakleaf lettuce |
A00LB | lettuce | lettuce | Lollo rosso |
A00JA | bell pepper | bell pepper | Sweet peppers |
A00QV | radish | radish | Radishes |
A00LM | radish | radish | Roman rocket and similar |
B2474 | radish | radish | Rocket |
A0DMX | tomato | tomato | Tomatoes |
A00HY | vine tomato | vine tomato | Cherry tomatoes |
Vegetable Parameters
Each vegetable type has specific cultivation parameters:
| Vegetable | Growing Days | Inside Temp (K) | Yield (kg/m2/month) | Electricity (kWh/kg) |
|---|---|---|---|---|
| eggplant | 50 | 291.15 (18C) | 3.15 | 0.5492 |
| cucumber | 32 | 291.15 (18C) | 4.36 | 0.1982 |
| lettuce | 60 | 281.15 (8C) | 1.74 | 0.4636 |
| bell pepper | 41 | 293.15 (20C) | 1.97 | 0.5746 |
| radish | 51 | 279.15 (6C) | 1.36 | 0.33798 |
| tomato | 127 | 291.15 (18C) | 4.66 | 0.2207 |
| vine tomato | 127 | 291.15 (18C) | 4.72 | 0.2099 |
Data sources: ProfiCost-Tool (Chollet et al., 2012), Swiss Association of Vegetable Producers technical handbook.
Covered Countries
The module has Meteonorm climate data for 28 European countries:
| Code | Country | Code | Country |
|---|---|---|---|
| AT | Austria | IT | Italy |
| BE | Belgium | LT | Lithuania |
| BG | Bulgaria | LU | Luxembourg |
| CH | Switzerland | LV | Latvia |
| CZ | Czech Republic | NL | Netherlands |
| DE | Germany | PL | Poland |
| DK | Denmark | PT | Portugal |
| EE | Estonia | RO | Romania |
| ES | Spain | SE | Sweden |
| FI | Finland | SI | Slovenia |
| FR | France | SK | Slovakia |
| GB | Great Britain | TR | Turkey |
| GR | Greece | HR | Croatia |
| HU | Hungary | IE | Ireland |
Missing EU countries: Cyprus, Malta
Exclusion Rules
The greenhouse model does NOT run if the product has any of these conservation tags:
| Term XID | Description |
|---|---|
J0001 | Generic Conserved |
J0136 | Frozen |
J0111 | Canned |
J0116 | Dried |
These products are excluded because conserved products don't require greenhouse production at the time of consumption - they were likely produced during the regular growing season.
Note: J0003 (Not Conserved) and J0131 (Cooled) do NOT exclude products from the greenhouse model.
Production Date Handling
The production date determines which months' climate data is used for heating calculations:
- If
activity_dateis specified on the product or any parent node, it's used - The harvest date is calculated as
production_date - 3 days - Growing days are backtracked from the harvest date based on the vegetable type
- Monthly heating requirements are calculated for each month in the growing period
Example: Tomatoes with production date March 15:
- Harvest date: March 12
- Growing period: 127 days (October 6 to March 12)
- Months with growing days: October (partial), November, December, January, February, March (partial)
Full Code Reference
Heating Calculation
The compute_required_heating method calculates heating demand in MJ per kg of vegetable:
def compute_required_heating(
self,
flow_country_code: str,
flow_processing_date: datetime,
vegetable_name: str,
) -> float:
"""Compute the necessary amount of heating."""
days_between_harvest_and_production_date = 3
harvest_date = flow_processing_date - timedelta(days=days_between_harvest_and_production_date)
# Get growing days per month based on harvest date and vegetable growth duration
monthly_growing_days = np.array(days_in_each_month(NUMBER_OF_GROWING_DAYS[vegetable_name], harvest_date))
# Load country-specific climate data
monthly_avg_outside_temp = np.array(
self.gfm_factory.aggregated_meteonorm_data[flow_country_code]["average_temperature"]
)
monthly_avg_solar_radiation = np.array(
self.gfm_factory.aggregated_meteonorm_data[flow_country_code]["average_solar_radiation"]
)
# Calculate heat balance
monthly_temp_difference = REQUIRED_INSIDE_TEMPERATURE[vegetable_name] - monthly_avg_outside_temp
monthly_required_heating_power_watts = (
Q_TRANS_FACTOR + Q_AIR_FACTOR
) * monthly_temp_difference - Q_SOLAR_FACTOR * monthly_avg_solar_radiation
# No negative heating (no cooling modeled)
monthly_required_heating_power_watts = np.array(
[val if val >= 0.0 else 0.0 for val in monthly_required_heating_power_watts]
)
# Convert power to energy
monthly_required_heating_energy_wh = monthly_required_heating_power_watts * monthly_growing_days * 24
monthly_required_heating_energy_kwh = monthly_required_heating_energy_wh / 1000
# Calculate yield during growing period
average_yield_kg_per_day = (
AVG_YIELD_PER_M2_PER_MONTH[vegetable_name] * AREA_OF_THE_WINDOWS_EXPOSED_TO_THE_SUN * 12 / 365
)
yield_in_growing_days_kg = average_yield_kg_per_day * sum(monthly_growing_days)
# Convert to MJ per kg
mj_in_kwh = 3.6
monthly_required_heating_energy_mj = monthly_required_heating_energy_kwh * mj_in_kwh
required_heating_energy_mj_for_kg_of_vegetable = (
np.sum(monthly_required_heating_energy_mj) / yield_in_growing_days_kg
)
return required_heating_energy_mj_for_kg_of_vegetable
Infrastructure Accounting
async def account_for_infrastructure(
self, calc_graph: CalcGraph, greenhouse_activity: FoodProcessingActivityNode, vegetable_name: str
) -> None:
"""Account for infrastructure of greenhouse."""
size_of_greenhouse_in_m2 = AREA_OF_THE_WINDOWS_EXPOSED_TO_THE_SUN
average_yield_in_kg_per_m2_and_month = AVG_YIELD_PER_M2_PER_MONTH[vegetable_name]
total_yield_per_year = average_yield_in_kg_per_m2_and_month * 12 * size_of_greenhouse_in_m2
# Calculate m2*year of building material per kg of crop
area_and_time_of_greenhouse_building_material = (
size_of_greenhouse_in_m2 / total_yield_per_year * self.node.production_amount.value
)
# Split between glass (60.4%) and plastic (39.6%)
glass_building_material = (
area_and_time_of_greenhouse_building_material * FRACTION_OF_GLASS_IN_BUILDING_MATERIAL
)
plastic_building_material = (
area_and_time_of_greenhouse_building_material * FRACTION_OF_PLASTIC_IN_BUILDING_MATERIAL
)
# Add glass and plastic flow nodes to the graph
# Connected to Ecoinvent processes for greenhouse infrastructure
Electricity Accounting
async def account_for_electrical_energy(
self,
calc_graph: CalcGraph,
greenhouse_activity: FoodProcessingActivityNode,
country_code: str,
vegetable_name: str,
) -> None:
"""Account for electrical energy consumption of greenhouse."""
energy_needed_per_kg = REQUIRED_ELECTRICITY_KWH_PER_KG[vegetable_name]
# Create electricity consumption flow
electricity_consumption_flow = FlowNode(
uid=UuidStr(uuid4()),
amount=QuantityProp(
value=energy_needed_per_kg * self.node.production_amount.value,
unit_term_uid=self.gfm_factory.kWh_term.uid,
),
)
# Connect to country-specific electricity market from cache
electrical_energy_node = self.gfm_factory.electricity_markets_cache.get(country_code)
Data Sources
Meteonorm Climate Data
Climate data is sourced from Meteonorm software (2016 data):
- Temperature: Monthly average outside temperature in Kelvin
- Solar radiation: Monthly average global solar irradiation in W/m2
The data is stored in the GFM cache and loaded at service startup:
meteonorm_average_temperature_{country_code}: Array of 12 monthly valuesmeteonorm_average_solar_radiation_{country_code}: Array of 12 monthly values
Heating Mixes
Country-specific heating mixes for greenhouse heating:
| Country | Brightway XID | Composition |
|---|---|---|
| CH (Switzerland) | EDB_4220cbaabca343c09b19415d5ab4079f_copy1 | 41.3% natural gas, 35.2% heating oil, 23.5% renewables |
| DE (Germany) | EDB_4220cbaabca343c09b19415d5ab4079f_copy2 | 28% coal, 21% natural gas, 15% fuel oil, 20% renewables, 16% other |
| NL (Netherlands) | EDB_4220cbaabca343c09b19415d5ab4079f | 86.2% natural gas, 0.1% heating oil, 13.7% renewables |
| EU other | EDB_4220cbaabca343c09b19415d5ab4079f_copy3 | 54% natural gas, 46% heating oil |
Infrastructure Materials
Ecoinvent processes for greenhouse building materials:
| Material | Ecoinvent ID | Unit |
|---|---|---|
| Glass greenhouse | ecoinvent 3.6 cutoff_0ce659c3cfd443a38761058ee62e3f10 | m2*year |
| Plastic tunnel | ecoinvent 3.6 cutoff_fddfe51c6959f41ac044089c3a892af7 | m2*year |
Calculation Example
Scenario: 1 kg of tomatoes, produced in Sweden on March 15
Step 1: Determine Growing Period
- Production date: March 15
- Harvest date: March 12 (3 days before production)
- Growing duration: 127 days
- Start date: November 5 (previous year)
Step 2: Calculate Monthly Growing Days
| Month | Days |
|---|---|
| November | 25 |
| December | 31 |
| January | 31 |
| February | 28 |
| March | 12 |
| Total | 127 |
Step 3: Calculate Heating Demand
Using Swedish climate data and tomato requirements (18C inside temperature):
- Monthly temperature differences calculated
- Solar radiation offset applied
- Negative values clamped to zero
Result: ~32.3 MJ/kg heating demand
Step 4: Calculate Infrastructure
Total yield/year = 4.66 kg/m2/month * 12 * 46,800 m2 = 2,616,576 kg/year
Building material per kg = 46,800 / 2,616,576 = 0.0179 m2*year/kg
Glass: 0.0179 * 0.604 = 0.0108 m2*year
Plastic: 0.0179 * 0.396 = 0.0071 m2*year
Step 5: Calculate Electricity
Electricity = 0.2207 kWh/kg * Swedish electricity mix emissions
Final Output
The module adds three flow nodes to the calculation graph:
- Heating flow (32.3 MJ) connected to Swedish heating mix process
- Glass infrastructure flow (0.0108 m2*year) connected to Ecoinvent glass process
- Plastic infrastructure flow (0.0071 m2*year) connected to Ecoinvent plastic process
- Electricity flow (0.2207 kWh) connected to Swedish electricity market
Known Limitations
Geographic Coverage
- Only 28 European countries have Meteonorm data
- No coverage for US, Canada, or other major producing regions
- No sub-country regional differentiation (important for large countries)
Crop Coverage
- Only 7 vegetable types are modeled (tomato, cucumber, lettuce, bell pepper, eggplant, radish, vine tomato)
- Herbs, chilis, strawberries, and other greenhouse crops are not covered
- Some crops like lamb's lettuce have data available but aren't implemented
Model Assumptions
- No heating required = no greenhouse assumed (may underestimate emissions for unheated greenhouses)
- Same infrastructure mix used for all countries (based on Swiss data)
- Organic production not differentiated (minimal impact difference found)
- No country-specific greenhouse growing seasons enforced
Data Age
- Meteonorm climate data is from 2016
- Heating mixes may not reflect current energy transitions
References
-
Stössel, F., Juraske, R., Pfister, S., & Hellweg, S. (2012). Life cycle inventory and carbon and water footprint of fruits and vegetables: application to a Swiss retailer. The International Journal of Life Cycle Assessment, 17(9), 1191-1202.
-
Eymann, L., et al. (2014). Gewächshausmodell v1.0. ZHAW Report for Eaternity.
-
Scharfy, D., et al. (2017). OFP Report - New Data ZHAW v1.3. Organic Food Print project report.
-
Chollet, D., et al. (2012). ProfiCost-Tool. Swiss Association of Vegetable Producers.
-
Meteonorm Software. https://meteonorm.com/