openEPD subset decision (v1)¶
1. Purpose¶
We adopt openEPD (Building Transparency / C-Change Labs) as the internal JSON schema for all EPD data in the chwr_LCA backend, per locked decision 5. For v1 we implement a deliberately narrow subset: the objects and fields required to satisfy every "INPUTS FROM EPD LIBRARY" block in docs/methodology/module-inputs.md across the ten included EN 15978 modules (decision 2), plus the extension points needed to carry proxy provenance (decision 7) and, where relevant, classification metadata (decision 19). We defer everything openEPD offers that our v1 module set does not consume — most notably the full concrete/steel sector specs extensions, bundle/signature machinery, and anything oriented at EC3's marketplace/procurement workflow rather than at LCA calculation. This decision interacts with decision 7 by defining exactly where proxy_provenance attaches (see the companion proxy-provenance-ext.md document, §2), and with decision 19 by explicitly placing classification codes outside the EPD object, on our domain schemas instead (§8).
2. openEPD version pinned for v1¶
- Library:
openepd(PyPI), reference implementation atgithub.com/cchangelabs/openepd(Apache-2.0). Note: this supersedesbuildingtransparency/openepd, which no longer resolves — C-Change Labs is the current maintainer and steward of the openEPD standard, docs now live atopen-epd-forum.org. - Version:
6.34.2 - Commit SHA:
1d48815c135e2d599583e7c10cd65ca706266ea1(tag6.34.2oncchangelabs/openepd, verified via GitHub API refs endpoint, Session 3B). - Pydantic compatibility note: Despite marketing language about a "Pydantic v1/v2 compatibility layer,"
openepd==6.34.2internally uses Pydantic v1 (viapydantic.v1compat module). Our domain schemas (proxy_provenance,material, etc.) are Pydantic v2. The boundary between them requires explicit dict serialization — do not attempt to subclassEpdusing Pydantic v2 tools. - This file is the single source of truth for the pin. Per locked decision 20, PROJECT.md references this section rather than duplicating the version string — when openEPD is bumped in Phase 2/3, only this file changes.
- Re-pin discipline: any move to v7, or any minor/patch bump that changes a field this document references, requires updating this file and re-running the seed-EPD regression suite (
backend/tests/data/test_seed_epd_integrity.py).
3. Included openEPD objects¶
Epd— the core object. Carries product identity, program operator, PCR reference, declared unit, impacts, geography, temporal validity.Org— organizations (manufacturer, program operator, verifier). Used to record who issued/verified the source EPD, including proxy source EPDs pulled in via the cascade.Pcr— Product Category Rules reference. Required per module-inputs.md's "PCR reference" field for every material.Plant(optional, lightweight) — only where a real EPDASA/ÖKOBAUDAT EPD identifies a specific manufacturing facility; not required for generic/proxy entries.Impacts/ImpactSet/ScopeSet*/Measurement— the impact measurement hierarchy.Impactsis adict[LCIAMethod, ImpactSet]; eachImpactSetcarries per-indicatorScopeSetmodels; eachScopeSetcarries per-moduleMeasurementobjects (mean,unit,rsd,dist). Used for every A1-A3/A4/A5/B4/B6/C1-C4/D indicator value we store.- Geography / date value types —
applicable_in(list of ISO 3166 country strings),date_of_issue(datetime),valid_until(datetime). extextension container — attachment point forproxy_provenance. A plaindict; useepd.set_ext_field('chwr', value)/epd.get_ext_field('chwr')helper methods.
4. Excluded openEPD objects and fields¶
- Sector
specsextensions (Concrete, Steel, Carpet, ...) — none of our v1 materials (earth-based, aggregate, binder, metal for reinforcement/roofing) require a formally registered sector PCR extension in v1. If a future material category needs one (e.g. concrete strength_28d for RC-frame comparison), it can be added without a schema migration sincespecsis additive. - Bundle format (
.epb) — designed for packaging EPD+PDF+translations for transfer between registries. We have no need to exchange bundles in v1; seed EPDs live as flat JSON files inbackend/app/data/seed_epds/. - Digital signature / data integrity field — optional security feature for registry-to-registry transfer; irrelevant to a single-deployment tool with its own provenance gate (CLAUDE.md).
- API client (
openepd.api.sync_client) — we do not call the live EC3/BuildingTransparency API at runtime (data sources are free/humanitarian registries per decision 4, ingested offline into seed files, not live-queried). We use only the Pydantic model layer (openepd.model), not the client layer. product_classesfor Masterformat/UNSPSC values — we do not populate Masterformat or UNSPSC; our classification taxonomy is Uniclass/IFC/UN-CPC (decision 19), which do not map onto this field. We leave it unpopulated rather than force a mismatched code into it (see §8).- Category Tree (
openepd.category) — a UX/browsing convenience for EC3's marketplace search; not needed for our calculation engine.
5. Field-level checklist — module inputs vs openEPD coverage¶
| Required field (module-inputs.md) | EN 15978 module | openEPD field (reconciled against 6.34.2) |
Status |
|---|---|---|---|
| EPD reference or proxy selection | A1-A3 | Epd.id (open-xpd-uuid) |
NATIVE |
| A1-A3 GWP | A1-A3 | Epd.impacts.__root__[LCIAMethod.EF_3_1].gwp.A1A2A3 → Measurement |
NATIVE |
| A1-A3 ODP | A1-A3 | Epd.impacts.__root__[LCIAMethod.EF_3_1].odp.A1A2A3 |
NATIVE |
| A1-A3 AP | A1-A3 | Epd.impacts.__root__[LCIAMethod.EF_3_1].ap.A1A2A3 |
NATIVE |
| A1-A3 EP | A1-A3 | Epd.impacts.__root__[LCIAMethod.EF_3_1].ep.A1A2A3 |
NATIVE |
| A1-A3 ADP-E | A1-A3 | Epd.impacts.__root__[LCIAMethod.EF_3_1].ADP_mineral.A1A2A3 |
NATIVE |
| A1-A3 ADP-F | A1-A3 | Epd.impacts.__root__[LCIAMethod.EF_3_1].ADP_fossil.A1A2A3 |
NATIVE |
| A1-A3 POCP | A1-A3 | Epd.impacts.__root__[LCIAMethod.EF_3_1].pocp.A1A2A3 |
NATIVE |
| A1-A3 WDP | A1-A3 | Epd.impacts.__root__[LCIAMethod.EF_3_1].WDP.A1A2A3 |
NATIVE (note uppercase WDP) |
| A1-A3 NW (Net Waste) | A1-A3 | — | OUT OF SCOPE for openEPD — NW is not an EF 3.1 standard indicator and has no ImpactSet field; waste flows live in Epd.output_flows (OutputFlowSet with nhwd, hwd, twd). Track in output_flows or defer to Phase 2. |
| D avoided-burden factor (per indicator) | D | Epd.impacts.__root__[LCIAMethod.EF_3_1].gwp.D (etc. per indicator) |
NATIVE, with the hard rule that D is never summed into A-C totals (§6) |
| Declared unit | A1-A3 | Epd.declared_unit: Amount → {qty: float, unit: str} |
NATIVE (note: field name is qty, not value) |
| Data type (product-specific / industry-average / generic / PROXY) | A1-A3 | — | MISSING → EXTENSION (proxy_provenance.cascade_step; step 1 = non-proxy) |
| Programme operator | A1-A3 | Epd.program_operator: Org |
NATIVE |
| Geographic representativeness (ISO 3166) | A1-A3 | Epd.applicable_in: list[str] (ISO 3166 country codes) |
NATIVE |
| Temporal representativeness (year, valid_until) | A1-A3 | Epd.date_of_issue: datetime, Epd.valid_until: datetime |
NATIVE |
| PCR reference | A1-A3 | Epd.pcr: Pcr |
NATIVE |
| Proxy provenance (cascade step, source EPD, adaptation factors) | A1-A3 | — | MISSING → EXTENSION (Epd.ext['chwr']['proxy_provenance']) |
| Pedigree score → GSD² | A1-A3 (feeds Monte Carlo) | Measurement.rsd is the output slot; 5-axis input score has no home |
MISSING → EXTENSION (extension computes and writes back into rsd at the boundary) |
| Truck/sea/rail/barge emission factor | A4 | — (transport-library data, not EPD data) | OUT OF SCOPE for openEPD — lives in transport_factors domain table |
| C3 GWP factor, biogenic release | C3 | — | RESOLVED: plain waste_processing_factors library table, NOT openEPD-shaped records (see §10 open question 3) |
| Thermal properties (λ, ρ, c, U-value) | B6 | — | OUT OF SCOPE for openEPD — material physical-property fields on domain Material schema |
6. Impact indicator representation¶
All eight v1 EF 3.1 indicators (GWP, ODP, AP, EP, ADP-E, ADP-F, POCP, WDP) map onto openEPD's Impacts / ImpactSet / ScopeSet* / Measurement hierarchy, which is EN 15804-module-aware and EF-3.1-compatible. Access pattern:
from openepd.model.lcia import LCIAMethod
impact_set = epd.impacts.__root__[LCIAMethod.EF_3_1]
gwp_a1a3: Measurement = impact_set.gwp.A1A2A3
Each Measurement carries mean, unit, rsd, dist. Unit strings use spaces not underscores (e.g. "kg CO2 eq", "kg CFC-11 eq", "kg Sb eq", "m3 world eq deprived", "MJ").
Module D is a hard exception: per EN 15804+A2, D is reported as a separate declared value and is never netted against the A-C total. Our schema enforces this at two levels: (1) openEPD naturally keeps D as its own module-scoped Measurement (ScopeSetGwp.D etc.); (2) our reporting service must have a hard assertion that D never enters the A-C summation path.
7. Declared unit handling¶
Conversion chain, BoQ quantity → indicator result:
- BoQ work item gives a quantity in m3, m2, or ml (metre lineaire), per
Quantity(value, unit). - Material recipe (assembly → material formula) resolves this to a raw-material quantity, still in
Quantity. - Material density (
kg/m3, from the domainMaterialschema, not from openEPD) converts volume/area/length-based quantities to mass in kg. - openEPD's
declared_uniton the selected EPD (Amount.qtyandAmount.unit) must match or be convertible to that mass. Where the EPD's declared unit is not kg (e.g. m3 for a bulk aggregate), we apply the EPD's own density/conversion factor (if published) or our material-properties density as a fallback, and record which was used. mass_kg × indicator_value_per_declared_unit = module contribution.
Compatibility confirmation: our Quantity(value: float, unit: UnitEnum) discipline is compatible with openEPD's declared-unit-plus-measurement approach. The adapter at the boundary is a thin converter function (openepd_measurement_to_quantity() / quantity_to_openepd_measurement()) in the quantities domain module.
8. Classification taxonomy carriage (interaction with decision 19)¶
openEPD's only native classification field is product_classes, scoped to Masterformat and UNSPSC — neither of which is our chosen taxonomy. Uniclass 2015, IFC entity codes, and UN CPC (decision 19) have no native home in the openEPD Epd object.
Decision: classification codes live on our internal Material/Assembly domain schemas (NOT on the EPD object itself), which reference the EPD by ID. Justification: (a) a single EPD (e.g. generic CEB block) may be referenced by many assemblies with different Uniclass/IFC codes — conflating them would force EPD duplication and break reuse; (b) ext is for genuinely EPD-scoped extension data; (c) UN CPC being material-level is the one code that could sit in product_classes in spirit, but we deliberately do not use product_classes to keep exactly one authoritative classification location.
9. Compatibility with epdx export (Phase 3)¶
Since epdx targets ILCD+EPD and openEPD is ILCD-compatible, the fields we use (native Epd/Org/Pcr/measurement objects) should round-trip cleanly. The real risk is our proxy_provenance extension: ILCD+EPD has no native "this is a proxy" concept, so epdx will likely silently drop ext.chwr.proxy_provenance on export. Flag for Phase 3: verify empirically whether epdx preserves arbitrary ext content; if dropped, decide whether proxy provenance needs a parallel non-normative annex in the ILCD export.
10. Open questions¶
- Exact Pydantic field paths: RESOLVED — see reconciliation table in §5 and the Session 3B log entry in ROADMAP.md.
- Commit SHA for tag
6.34.2: RESOLVED — SHA1d48815c135e2d599583e7c10cd65ca706266ea1, recorded in §2. - C3/C4 EoL processing factors: RESOLVED per orchestrator decision — model as a plain
waste_processing_factorsdomain library table, NOT as openEPD-shaped records. Rationale: free-source EoL factors (IPCC EFDB, peer-reviewed) have no programme operator and no PCR, so forcing them into theEpdschema would fail the seed-EPD provenance gate. Thelca.c3andlca.c4modules will consume this table directly. Full spec deferred to Phase 2 C3/C4 module design. epdxext-block preservation behavior — empirical Phase 3 task (§9).- Whether any v1 earth-material category (adobe, CEB, CSEB) will eventually need a registered sector
specsextension for comparability — out of scope for v1.