Sustainability & Carbon Analytics
RevitPy provides a full sustainability analysis module for whole-life carbon assessment, Environmental Product Declaration (EPD) management, building emissions compliance checking, and report generation for green building certification workflows.
Overview
The revitpy.sustainability module provides five core components:
CarbonCalculator– Calculates embodied carbon for building materials using EPD data, aggregates results into building-level summaries, and benchmarks against RIBA 2030 Climate Challenge targets.EpdDatabase– Manages EPD records with a local cache of generic values, fuzzy keyword matching, and optional async lookup against the EC3 (Embodied Carbon in Construction Calculator) API.ComplianceChecker– Verifies building performance against LL97, BERDO, EPBD, and ASHRAE 90.1 standards with pass/fail results and improvement recommendations.SustainabilityReporter– Generates sustainability reports in JSON, CSV, and HTML formats, with certification documentation helpers for LEED, BREEAM, DGNB, and Green Star.MaterialExtractor– Extracts and classifies material quantities from building elements.
The module also exposes three convenience functions for quick one-off operations:
from revitpy.sustainability import (
CarbonCalculator,
EpdDatabase,
ComplianceChecker,
SustainabilityReporter,
MaterialExtractor,
# Convenience functions
calculate_carbon,
check_compliance,
generate_report,
# Types and enums
MaterialData,
EpdRecord,
CarbonResult,
BuildingCarbonSummary,
ComplianceResult,
EnergyEnvelopeData,
CarbonBenchmark,
LifecycleStage,
CertificationSystem,
ComplianceStandard,
ReportFormat,
)
CarbonCalculator
CarbonCalculator computes embodied carbon for building materials by resolving GWP (Global Warming Potential) factors from an EPD database. It supports mass-based and volume-based calculation, building-level summarization, and benchmarking.
Creating a Calculator
from revitpy.sustainability import CarbonCalculator, EpdDatabase
# With default EPD database
calculator = CarbonCalculator()
# With a custom EPD database
epd_db = EpdDatabase(api_token="your-ec3-token")
calculator = CarbonCalculator(epd_database=epd_db)
Calculating Embodied Carbon
calculate takes a list of MaterialData and optional lifecycle stages, looks up EPD records, and returns a list of CarbonResult instances. The calculator prefers mass-based calculation (mass_kg * gwp_per_kg); when mass is zero it falls back to volume-based (volume_m3 * gwp_per_m3).
from revitpy.sustainability import MaterialData, LifecycleStage
materials = [
MaterialData(
name="Concrete",
category="Concrete",
mass_kg=50000.0,
volume_m3=20.8,
level="Level 1",
system="Structure",
),
MaterialData(
name="Steel",
category="Metals",
mass_kg=12000.0,
volume_m3=1.5,
level="Level 1",
system="Structure",
),
MaterialData(
name="Timber",
category="Wood",
mass_kg=3000.0,
volume_m3=6.0,
level="Level 2",
system="Framing",
),
]
# Default: A1-A3 product stages
results = calculator.calculate(materials)
# Specify lifecycle stages explicitly
results = calculator.calculate(
materials,
lifecycle_stages=[
LifecycleStage.A1_RAW_MATERIALS,
LifecycleStage.A2_TRANSPORT,
LifecycleStage.A3_MANUFACTURING,
LifecycleStage.A4_TRANSPORT_SITE,
LifecycleStage.A5_CONSTRUCTION,
],
)
for r in results:
print(f"{r.material.name}: {r.embodied_carbon_kgco2e:.2f} kgCO2e ({r.calculation_method})")
Materials without a matching EPD record are silently skipped. If the calculation itself fails, a CarbonCalculationError is raised.
Summarizing Results
summarize aggregates a list of CarbonResult into a BuildingCarbonSummary with totals broken down by material, building system, level, and lifecycle stage:
summary = calculator.summarize(results)
print(f"Total: {summary.total_embodied_carbon_kgco2e:.2f} kgCO2e")
print(f"Materials assessed: {summary.material_count}")
print(f"Calculated: {summary.calculation_date}")
# Breakdowns
for material, carbon in summary.by_material.items():
print(f" {material}: {carbon:.2f} kgCO2e")
for system, carbon in summary.by_system.items():
print(f" {system}: {carbon:.2f} kgCO2e")
for level, carbon in summary.by_level.items():
print(f" {level}: {carbon:.2f} kgCO2e")
for stage, carbon in summary.by_lifecycle_stage.items():
print(f" {stage}: {carbon:.2f} kgCO2e")
Benchmarking
benchmark compares a building summary against RIBA 2030 Climate Challenge targets. Ratings are assigned based on the ratio of actual to target intensity:
bench = calculator.benchmark(
summary,
building_area_m2=2500.0,
building_type="office",
)
print(f"Actual: {bench.actual_kgco2e_per_m2} kgCO2e/m2")
print(f"Target: {bench.target_kgco2e_per_m2} kgCO2e/m2")
print(f"Source: {bench.benchmark_source}")
print(f"Rating: {bench.rating}")
print(f"Percentile: {bench.percentile}")
Benchmark ratings are assigned as follows:
| Ratio (actual/target) | Rating | Percentile |
|---|---|---|
| 0.50 or below | Excellent | 95 |
| 0.51 – 0.75 | Good | 75 |
| 0.76 – 1.00 | Acceptable | 50 |
| 1.01 – 1.25 | Below Average | 25 |
| Above 1.25 | Poor | 10 |
Built-in RIBA 2030 targets (kgCO2e/m2):
| Building Type | Target |
|---|---|
residential |
300.0 |
office |
350.0 |
school |
300.0 |
retail |
350.0 |
industrial |
400.0 |
default |
350.0 |
Async Calculation
calculate_async runs the same calculation with async support and an optional progress callback:
import asyncio
async def main():
def on_progress(completed: int, total: int):
print(f"Progress: {completed}/{total}")
results = await calculator.calculate_async(
materials,
lifecycle_stages=[LifecycleStage.A1_RAW_MATERIALS],
progress=on_progress,
)
asyncio.run(main())
The calculator yields control to the event loop every 50 materials to avoid blocking.
EpdDatabase
EpdDatabase manages Environmental Product Declaration records with a local cache of generic industry-average values, fuzzy keyword matching, and optional async queries against the EC3 API.
Creating a Database
from revitpy.sustainability import EpdDatabase
# Default: generic values only
epd_db = EpdDatabase()
# With EC3 API token for remote lookups
epd_db = EpdDatabase(api_token="your-ec3-api-token")
# With a local cache file
epd_db = EpdDatabase(cache_path="/path/to/epd_cache.json")
Built-in Generic EPDs
The database ships with generic EPD values (ICE Database / industry averages) for eight common materials:
| Key | Material | Category | GWP/kg | GWP/m3 |
|---|---|---|---|---|
concrete |
Concrete | Concrete | 0.13 | 312.0 |
steel |
Steel | Metals | 1.55 | 12167.5 |
timber |
Timber | Wood | 0.45 | 225.0 |
glass |
Glass | Glass | 0.86 | 2150.0 |
aluminum |
Aluminum | Metals | 8.24 | 22248.0 |
brick |
Brick | Masonry | 0.24 | 432.0 |
insulation |
Insulation | Insulation | 1.86 | 55.8 |
gypsum |
Gypsum Board | Interior Finishes | 0.39 | 312.0 |
All generic EPDs cover lifecycle stages A1–A3 and use "generic-ice" as their source.
Looking Up EPDs
lookup searches the local cache using exact match, then fuzzy keyword matching, then category-based generic fallback:
# Exact match
epd = epd_db.lookup("concrete")
print(epd.material_name) # "Concrete"
print(epd.gwp_per_kg) # 0.13
# Fuzzy match (substring matching against cache keys)
epd = epd_db.lookup("reinforced concrete") # Matches "concrete"
# Category fallback
epd = epd_db.lookup("unknown material", category="Metals") # Matches steel/aluminum
# No match
epd = epd_db.lookup("exotic material") # Returns None
Generic EPD Lookup
get_generic_epd searches generic EPDs by category name:
epd = epd_db.get_generic_epd("Concrete")
epd = epd_db.get_generic_epd("Metals") # Matches Steel
epd = epd_db.get_generic_epd("Wood") # Matches Timber
Async Lookups (EC3 API)
When an API token is configured, lookup_async queries the EC3 API if local lookup returns nothing:
import asyncio
async def main():
epd_db = EpdDatabase(api_token="your-token")
# Tries local cache first, then queries EC3 API
epd = await epd_db.lookup_async("low-carbon concrete", category="Concrete")
if epd:
print(f"{epd.material_name}: {epd.gwp_per_kg} kgCO2e/kg (source: {epd.source})")
asyncio.run(main())
search_async performs a broader search across local cache and the EC3 API:
async def main():
results = await epd_db.search_async("concrete", limit=10)
for epd in results:
print(f"{epd.material_name} ({epd.source}): {epd.gwp_per_kg} kgCO2e/kg")
asyncio.run(main())
Cache Management
# Save current cache to disk
epd_db.save_cache("/path/to/epd_cache.json")
# Load cache from disk (also done automatically at init if cache_path is set)
epd_db.load_cache("/path/to/epd_cache.json")
ComplianceChecker
ComplianceChecker verifies building performance data against major emissions and energy standards. Each check returns a ComplianceResult with pass/fail status, threshold comparison, and improvement recommendations.
Creating a Checker
from revitpy.sustainability import ComplianceChecker
checker = ComplianceChecker()
Generic Check Interface
The check method dispatches to the correct standard-specific checker:
from revitpy.sustainability import ComplianceStandard
result = checker.check(
ComplianceStandard.LL97,
{"area_sqft": 50000, "annual_emissions_tco2e": 300, "occupancy_type": "office"},
)
print(result.passed) # True or False
print(result.actual_value) # Actual intensity
print(result.threshold) # Limit for the standard
print(result.unit) # e.g. "tCO2e/sqft/year"
print(result.recommendations) # List of improvement suggestions
print(result.details) # Standard-specific details dict
NYC Local Law 97 (LL97)
Checks carbon intensity against LL97 2024–2029 limits by occupancy type:
result = checker.check_ll97({
"area_sqft": 50000,
"annual_emissions_tco2e": 300,
"occupancy_type": "office", # optional, defaults to "default"
})
Required keys: area_sqft, annual_emissions_tco2e. Optional: occupancy_type.
LL97 limits (tCO2e/sqft/year, 2024–2029 period):
| Occupancy Type | Limit |
|---|---|
office |
0.00846 |
residential |
0.00675 |
retail |
0.01074 |
hotel |
0.00987 |
healthcare |
0.02381 |
education |
0.00758 |
warehouse |
0.00574 |
Boston BERDO
Checks emissions intensity against Boston BERDO standards:
result = checker.check_berdo({
"area_sqft": 50000,
"annual_emissions_kgco2e": 200000,
"building_type": "office", # optional
})
Required keys: area_sqft, annual_emissions_kgco2e. Optional: building_type.
BERDO limits (kgCO2e/sqft/year):
| Building Type | Limit |
|---|---|
office |
5.4 |
residential |
3.6 |
retail |
6.1 |
education |
4.8 |
EU EPBD
Checks primary energy demand against the Energy Performance of Buildings Directive:
result = checker.check_epbd({
"area_m2": 5000,
"primary_energy_kwh": 550000,
"building_type": "office", # optional
})
Required keys: area_m2, primary_energy_kwh. Optional: building_type.
EPBD limits (kWh/m2/year):
| Building Type | Limit |
|---|---|
residential |
100.0 |
office |
120.0 |
retail |
130.0 |
education |
110.0 |
ASHRAE 90.1 Envelope
Checks building envelope thermal performance against ASHRAE 90.1-2019 requirements for climate zone 4A. This method accepts an EnergyEnvelopeData dataclass directly:
from revitpy.sustainability import EnergyEnvelopeData
envelope = EnergyEnvelopeData(
wall_r_value=15.0,
roof_r_value=30.0,
window_u_value=0.32,
glazing_ratio=0.35,
air_tightness=3.0, # optional
)
result = checker.check_ashrae(envelope)
The method can also be called through the generic check interface using a dictionary:
result = checker.check(
ComplianceStandard.ASHRAE_90_1,
{
"wall_r_value": 15.0,
"roof_r_value": 30.0,
"window_u_value": 0.32,
"glazing_ratio": 0.35,
},
)
ASHRAE 90.1-2019 envelope requirements (climate zone 4A):
| Criterion | Requirement |
|---|---|
| Wall R-value | Minimum 13.0 |
| Roof R-value | Minimum 25.0 |
| Window U-value | Maximum 0.38 |
| Glazing ratio | Maximum 0.40 |
The result’s actual_value is a compliance ratio (0.0 to 1.0) representing the fraction of criteria passed.
Getting Recommendations
get_recommendations provides improvement suggestions based on a compliance result:
recs = checker.get_recommendations(result)
for rec in recs:
print(f" - {rec}")
When the check passes, the single recommendation is "Compliance achieved. Consider exceeding targets.". When it fails, standard-specific recommendations are provided (e.g., HVAC upgrades for LL97, envelope insulation for BERDO, renewable energy for EPBD).
SustainabilityReporter
SustainabilityReporter generates formatted reports from a BuildingCarbonSummary and produces certification documentation for green building rating systems.
Creating a Reporter
from revitpy.sustainability import SustainabilityReporter
reporter = SustainabilityReporter()
Generating Reports
The generate method dispatches to the appropriate format handler. When output_path is provided the report is written to disk and the Path is returned; otherwise the content is returned as a string.
from revitpy.sustainability import ReportFormat
# JSON report (returned as string)
json_content = reporter.generate(summary, format=ReportFormat.JSON)
# CSV report written to disk
path = reporter.generate(summary, format=ReportFormat.CSV, output_path="report.csv")
# HTML report written to disk
path = reporter.generate(summary, format=ReportFormat.HTML, output_path="report.html")
Format-Specific Methods
Each format also has a dedicated method:
# JSON
json_str = reporter.to_json(summary)
path = reporter.to_json(summary, path="output/report.json")
# CSV (rows sorted by carbon contribution, descending)
csv_str = reporter.to_csv(summary)
path = reporter.to_csv(summary, path="output/report.csv")
# HTML (uses Jinja2 if available, falls back to string formatting)
html_str = reporter.to_html(summary)
path = reporter.to_html(summary, path="output/report.html")
Certification Documentation
generate_certification_docs produces a dictionary of documentation content for green building certification applications:
from revitpy.sustainability import CertificationSystem
docs = reporter.generate_certification_docs(summary, CertificationSystem.LEED)
print(docs["certification_system"]) # "LEED"
print(docs["total_embodied_carbon_kgco2e"]) # 25100.0
print(docs["credits"]["MRc1"]["name"]) # "Building Life-Cycle Impact Reduction"
print(docs["credits"]["MRc1"]["potential_points"]) # 3
docs = reporter.generate_certification_docs(summary, CertificationSystem.BREEAM)
print(docs["credits"]["Mat01"]["name"]) # "Life Cycle Impacts"
print(docs["credits"]["Mat01"]["potential_credits"]) # 6
Supported certification systems and their credit mappings:
| System | Credit Code | Credit Name |
|---|---|---|
CertificationSystem.LEED |
MRc1 |
Building Life-Cycle Impact Reduction |
CertificationSystem.BREEAM |
Mat01 |
Life Cycle Impacts |
CertificationSystem.DGNB |
ENV1.1 |
Life Cycle Assessment |
CertificationSystem.GREENSTAR |
Materials |
Life Cycle Impacts |
Convenience Functions
For quick one-off operations without manually creating class instances:
from revitpy.sustainability import (
calculate_carbon,
check_compliance,
generate_report,
MaterialData,
ComplianceStandard,
ReportFormat,
)
# Calculate carbon (creates CarbonCalculator internally)
materials = [
MaterialData(name="Concrete", category="Concrete", mass_kg=50000.0),
]
results = calculate_carbon(materials)
# Check compliance (creates ComplianceChecker internally)
result = check_compliance(
ComplianceStandard.LL97,
{"area_sqft": 50000, "annual_emissions_tco2e": 300},
)
# Generate report (creates SustainabilityReporter internally)
content = generate_report(summary, format=ReportFormat.JSON, output_path="report.json")
Enum Reference
LifecycleStage
EN 15978 lifecycle stages for whole-life carbon assessment:
| Member | Value | Description |
|---|---|---|
A1_RAW_MATERIALS |
"A1" |
Raw material extraction and processing |
A2_TRANSPORT |
"A2" |
Transport to manufacturer |
A3_MANUFACTURING |
"A3" |
Manufacturing |
A4_TRANSPORT_SITE |
"A4" |
Transport to construction site |
A5_CONSTRUCTION |
"A5" |
Construction and installation |
B1_USE |
"B1" |
Use stage |
B2_MAINTENANCE |
"B2" |
Maintenance |
B3_REPAIR |
"B3" |
Repair |
B4_REPLACEMENT |
"B4" |
Replacement |
B5_REFURBISHMENT |
"B5" |
Refurbishment |
B6_ENERGY |
"B6" |
Operational energy use |
B7_WATER |
"B7" |
Operational water use |
C1_DEMOLITION |
"C1" |
Demolition |
C2_TRANSPORT |
"C2" |
Transport to disposal |
C3_WASTE |
"C3" |
Waste processing |
C4_DISPOSAL |
"C4" |
Disposal |
D_REUSE |
"D" |
Reuse, recovery, and recycling potential |
CertificationSystem
| Member | Value | Description |
|---|---|---|
LEED |
"LEED" |
USGBC LEED rating system |
BREEAM |
"BREEAM" |
BRE Environmental Assessment Method |
WELL |
"WELL" |
WELL Building Standard |
GREENSTAR |
"Green Star" |
Green Building Council of Australia |
DGNB |
"DGNB" |
German Sustainable Building Council |
ComplianceStandard
| Member | Value | Description |
|---|---|---|
LL97 |
"LL97" |
NYC Local Law 97 carbon intensity limits |
BERDO |
"BERDO" |
Boston Building Emissions Reduction and Disclosure Ordinance |
EPBD |
"EPBD" |
EU Energy Performance of Buildings Directive |
ASHRAE_90_1 |
"ASHRAE 90.1" |
ASHRAE 90.1 envelope thermal requirements |
ReportFormat
| Member | Value | Description |
|---|---|---|
JSON |
"json" |
Structured JSON output |
CSV |
"csv" |
Comma-separated values |
HTML |
"html" |
Formatted HTML document |
Dataclass Reference
MaterialData
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
– | Material name |
category |
str |
– | Material category |
volume_m3 |
float |
0.0 |
Volume in cubic meters |
area_m2 |
float |
0.0 |
Area in square meters |
mass_kg |
float |
0.0 |
Mass in kilograms |
density_kg_m3 |
float or None |
None |
Density in kg/m3 |
element_id |
str or None |
None |
Source Revit element ID |
level |
str or None |
None |
Building level |
system |
str or None |
None |
Building system |
EpdRecord
| Field | Type | Default | Description |
|---|---|---|---|
material_name |
str |
– | Material name |
category |
str |
– | Material category |
gwp_per_kg |
float |
– | GWP per kilogram (kgCO2e/kg) |
gwp_per_m3 |
float or None |
None |
GWP per cubic meter (kgCO2e/m3) |
source |
str |
"generic" |
Data source identifier |
lifecycle_stages |
list[LifecycleStage] |
[] |
Applicable lifecycle stages |
valid_until |
str or None |
None |
EPD expiration date |
manufacturer |
str or None |
None |
Product manufacturer |
CarbonResult
| Field | Type | Default | Description |
|---|---|---|---|
material |
MaterialData |
– | Source material data |
epd |
EpdRecord |
– | EPD record used for calculation |
embodied_carbon_kgco2e |
float |
– | Calculated embodied carbon |
lifecycle_stages |
list[LifecycleStage] |
[] |
Included lifecycle stages |
calculation_method |
str |
"mass_based" |
Method used ("mass_based", "volume_based", or "no_data") |
BuildingCarbonSummary
| Field | Type | Default | Description |
|---|---|---|---|
total_embodied_carbon_kgco2e |
float |
– | Total embodied carbon |
by_material |
dict[str, float] |
{} |
Carbon totals by material name |
by_system |
dict[str, float] |
{} |
Carbon totals by building system |
by_level |
dict[str, float] |
{} |
Carbon totals by building level |
by_lifecycle_stage |
dict[str, float] |
{} |
Carbon totals by lifecycle stage |
material_count |
int |
0 |
Number of materials in the summary |
calculation_date |
str |
"" |
ISO timestamp of the calculation |
ComplianceResult
| Field | Type | Default | Description |
|---|---|---|---|
standard |
ComplianceStandard |
– | The standard checked |
passed |
bool |
– | Whether the check passed |
threshold |
float |
– | Limit value for the standard |
actual_value |
float |
– | Actual measured/calculated value |
unit |
str |
– | Unit of measurement |
recommendations |
list[str] |
[] |
Improvement recommendations |
details |
dict[str, Any] |
{} |
Standard-specific detail data |
EnergyEnvelopeData
| Field | Type | Default | Description |
|---|---|---|---|
wall_r_value |
float |
– | Wall thermal resistance |
roof_r_value |
float |
– | Roof thermal resistance |
window_u_value |
float |
– | Window thermal transmittance |
glazing_ratio |
float |
– | Window-to-wall ratio (0.0 to 1.0) |
air_tightness |
float or None |
None |
Air tightness value |
CarbonBenchmark
| Field | Type | Default | Description |
|---|---|---|---|
actual_kgco2e_per_m2 |
float |
– | Actual carbon intensity |
target_kgco2e_per_m2 |
float |
– | Target carbon intensity |
benchmark_source |
str |
– | Source of the benchmark (e.g., "RIBA 2030 Climate Challenge") |
rating |
str |
– | Performance rating |
percentile |
float or None |
None |
Estimated percentile ranking |
Full Example
A complete workflow from material data through carbon calculation, compliance checking, and report generation:
from revitpy.sustainability import (
CarbonCalculator,
EpdDatabase,
ComplianceChecker,
SustainabilityReporter,
MaterialData,
LifecycleStage,
ComplianceStandard,
EnergyEnvelopeData,
CertificationSystem,
ReportFormat,
)
# 1. Set up the EPD database
epd_db = EpdDatabase(cache_path="epd_cache.json")
# 2. Define materials
materials = [
MaterialData(name="Concrete", category="Concrete", mass_kg=120000.0, volume_m3=50.0, level="Level 1", system="Structure"),
MaterialData(name="Steel", category="Metals", mass_kg=25000.0, volume_m3=3.2, level="Level 1", system="Structure"),
MaterialData(name="Timber", category="Wood", mass_kg=8000.0, volume_m3=16.0, level="Level 2", system="Framing"),
MaterialData(name="Glass", category="Glass", mass_kg=4000.0, volume_m3=1.6, level="Level 1", system="Facade"),
MaterialData(name="Insulation", category="Insulation", mass_kg=1500.0, volume_m3=50.0, level="Level 1", system="Envelope"),
]
# 3. Calculate embodied carbon
calculator = CarbonCalculator(epd_database=epd_db)
results = calculator.calculate(materials)
summary = calculator.summarize(results)
# 4. Benchmark against RIBA 2030
bench = calculator.benchmark(summary, building_area_m2=3000.0, building_type="office")
print(f"Rating: {bench.rating} ({bench.actual_kgco2e_per_m2:.1f} vs {bench.target_kgco2e_per_m2:.1f} kgCO2e/m2)")
# 5. Check compliance
checker = ComplianceChecker()
ll97 = checker.check_ll97({
"area_sqft": 32000,
"annual_emissions_tco2e": 200,
"occupancy_type": "office",
})
print(f"LL97: {'PASS' if ll97.passed else 'FAIL'}")
ashrae = checker.check_ashrae(EnergyEnvelopeData(
wall_r_value=15.0,
roof_r_value=30.0,
window_u_value=0.32,
glazing_ratio=0.35,
))
print(f"ASHRAE 90.1: {'PASS' if ashrae.passed else 'FAIL'}")
# 6. Generate reports
reporter = SustainabilityReporter()
reporter.generate(summary, format=ReportFormat.HTML, output_path="sustainability_report.html")
reporter.generate(summary, format=ReportFormat.JSON, output_path="sustainability_report.json")
# 7. Generate certification docs
leed_docs = reporter.generate_certification_docs(summary, CertificationSystem.LEED)
print(f"LEED MRc1 potential points: {leed_docs['credits']['MRc1']['potential_points']}")
# 8. Save EPD cache for next run
epd_db.save_cache("epd_cache.json")