Embodied carbon factors (A1–A3) as a versioned, CI-validated dataset — plus a tool that prices an IFC model with them.
The current landscape is paywalled spreadsheets, PDFs with no machine-readable form, and consultancy tools whose numbers you cannot audit. Meanwhile whole-life carbon is moving from voluntary to mandatory across the EU, the UK, the Nordics and increasingly elsewhere. If the factors behind a submission can't be traced, neither can the submission.
So this repo takes one position and enforces it in CI:
A number without provenance is not data.
Every row names its source, its dataset version, its publication date, the date
somebody retrieved it, and whether a human has actually read the number off the
source. ecf validate rejects rows that don't — and says which field is missing
and why it's required.
ERROR E130 data/factors/DE/de-mystery.toml:source missing [source] table; a number without provenance is not data
ERROR E131 data/factors/DE/de-mystery.toml:source.published required for tier=secondary
ERROR E121 data/factors/DE/de-mystery.toml:declared.a1_a3_kgco2e 245000 kgCO2e/m3 is outside the plausible band for 'concrete' (40–900); check for a unit or 1000x error
28 seed rows, 0 verified. Every shipped row is transcribed from a public
dataset (ÖKOBAUDAT, ICE v3, EC3, EPD-Norge) and is marked unverified: nobody has
yet opened the source record and confirmed the number. The checker says so on
every run, ecf coverage prints 0 verified (0%), and any total computed from
them carries an UNVERIFIED DATA banner.
That is the intended starting state, not an oversight. The scaffolding — schema, rules, review workflow — is what's finished. Verifying rows is the work, and it's the most useful PR you can send. Do not put these numbers in a submission.
Python ≥ 3.11, no runtime dependencies.
pip install -e ".[dev]"Editable, because the dataset is files in this repo rather than package data —
ecf finds data/ by walking up from the code. For a non-editable install,
point it at a checkout with ECF_DATA_ROOT=/path/to/ecfactors/data or --data.
ecf calc tests/fixtures/sample.ifcEmbodied carbon A1-A3 — sample.ifc
==================================
Total 22.84 tCO2e
Mapping default
Line items 9 counted, 1 unresolved
!! UNVERIFIED DATA: this total uses factor rows that no human has checked against the cited source.
By category
-----------
concrete 14.30 tCO2e 62.6% 58.38 m3
steel 7.94 tCO2e 34.8% 6,672.50 kg
insulation 331.8 kgCO2e 1.5% 245.76 kg
gypsum 262.1 kgCO2e 1.1% 81.92 m2
Unmapped materials (add a rule, or a factor)
-------------------------------------------
Unobtanium Panel 120.00 m2
The IFC reader is stdlib-only — a small ISO 10303-21 parser, no ifcopenshell. It
handles what exporters actually emit: IfcElementQuantity quantity sets, material
layer sets split by layer thickness, profile and constituent sets, material lists,
type-inherited materials, and millimetre models scaled to metres.
Nothing is silently dropped. A material with no mapping rule, an element with no material, a per-kg factor with no density to convert against — each is reported as a line you can act on, because a total with invisible gaps is worse than no total.
--format json for tooling, --format md for a PR comment, --fail-on-unmapped
and --fail-on-unverified to gate a build.
ecf calc model.ifc --region VNSwaps in the local row wherever one exists. Vietnam has no EPD programme, so
data/factors/VN/ currently holds explicit proxies — basis = "proxy",
proxy_for = "DE", and a note saying which way the real number probably differs.
The total may not move; what changes is that the report tells you it's a proxy.
ecf list --region VN # ? marks unverified rows
ecf show de-clt # the row with its full provenance
ecf coverage # where the gaps arerows per region x category (v = verified)
DE EU GLO NO SG UK US VN
concrete 3 . . 1 . 1 1 1
insulation 3 . . . . . . .
steel 2 . 1 . . 1 1 1
timber 3 . . . . 1 . .
28 rows, 0 verified (0%)
84 of 104 region/category cells empty
data/factors/<REGION>/<id>.toml, one material per file — see
docs/schema.md. Full rule list in docs/rules.md.
Two provenance tiers. primary is one EPD and requires its registration number,
programme operator and expiry date. secondary is a row in a published dataset
and requires the dataset name, its version, and the record identifier. Both
require an https URL, a publication date, a retrieval date and a standard.
Values are A1–A3 only. Biogenic carbon is carried separately and never netted off, because netting it makes timber look free.
| Job | Blocks a merge? | |
|---|---|---|
pytest + ecf validate |
yes | schema and provenance errors |
| provenance backlog | no | prints unverified count and coverage into the run summary |
| PR scope | yes | one material, or one region, per PR |
| link rot (weekly) | no | probes every source URL, opens an issue when one dies |
The weekly schedule matters: an EPD valid on merge day expires on its own
schedule. E135 catches it the following Monday.
CONTRIBUTING.md. Short version: one material or one region per PR, and the highest-value contribution is verifying a row that already exists — open the source, read the number, sign it with your handle and the date.
ecf new vn-cement-pcb40 --region VN --category cement --unit kgCode MIT. Data CC0 (data/LICENSE-DATA) — the rows are citations to third-party
sources, which keep their own terms.