Skip to content

feat(seed): restructure industrial processes into IPPU branches - #608

Closed
mrivas00 wants to merge 3 commits into
mainfrom
feat/mati/industrial-processes-ippu-branches
Closed

feat(seed): restructure industrial processes into IPPU branches#608
mrivas00 wants to merge 3 commits into
mainfrom
feat/mati/industrial-processes-ippu-branches

Conversation

@mrivas00

@mrivas00 mrivas00 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Context

Two reviewers flagged the same gap in Alcance 1: the industrial-process catalogue only covers cement, glass, steel and zinc, so a company running any other process has nowhere to declare it.

Gabriela: si la empresa realiza procesos industriales distintos a acero, fundición de metales, cemento o vidrio (por ejemplo industria química), no hay dónde incorporarlo.

Gianluca: en emisiones directas, ¿no es muy restrictivo solo acero, cinc y vidrio?

They are right, and the standards say why. The current set of four is an arbitrary subset of the IPCC IPPU tree: it includes glass and zinc, which the GHG Protocol's own sector table does not list, and omits lime, aluminium, chemicals and pulp & paper, which it does (GHG Protocol Corporate Standard, Appendix D "Industry Sectors and Scopes", pp. 92–93).

Nothing here needs code. The catalogue is seed data plus the maintainer screens, and no migration has ever touched methodology content — migrations in this repo are schema-only.

Two options, and why there are two PRs

Neither standard requires a subcategory per material. The GHG Protocol's own Alcance 1 taxonomy has a single "process emissions" bucket (ch. 6, p. 41), and subdividing by source type is explicitly optional information (ch. 9, p. 63). The IPCC, meanwhile, structures IPPU as a two-level hierarchy — branch (2A mineral, 2B chemical, 2C metal, 2H other) then leaf (2A1 cement, 2A2 lime, …), with an explicit Other (please specify) leaf on every branch (2006 GL Vol. 3, Fig. 1.1, p. 1.5).

So the real question is only where the boundary between subcategory and dimension falls. Both options cover the same IPPU leaves.

Option A — IPPU branches (this PR) Option B — one subcategory per process
Branch feat/mati/industrial-processes-ippu-branches feat/mati/industrial-processes-per-process
Subcategories in Alcance 1 11 → 11 11 → 18
…of which industrial 4 → 4 4 → 11
Shape subcategory = branch, ProcesoMaterial o método cascade subcategory = process, one flat dimension
Existing four subcategories replaced untouched
Dimension values / seeded IPCC factors 79 / 41 66 / 33
Adding a process later a dimension value in the maintainer UI, no release new subcategory + explanation + release
Reporting granularity per branch (2A / 2B / 2C / 2H) per process
Reduction-plan initiatives per branch — cement and glass share a bucket per process, exact
Explanation guides 4 11
Installed deployments the four existing subcategories need rework via the maintainer screens purely additive

The costs are real on both sides and neither is free:

  • A collapses the per-process breakdown in reports (carbon_inventory_subtotals_view aggregates by subcategory) and blunts initiative targeting, because getSuggestedReductionPlan filters only by subcategoryId. ReductionPlanInitiative already has dimensionValue1Id / dimensionValue2Id columns, so scoping initiatives to the process later is a query change, not a schema change.
  • B keeps that granularity but grows Alcance 1 to 18 subcategories, 11 of them industrial. useSubcategoryPreselectionData lists every subcategory of the methodology (recommended ones merely sort first), so every user — a bakery, a law firm — scrolls past all of them.

Recommendation: A. It matches the IPCC hierarchy, keeps the subcategory count flat, and turns "there is nowhere to put my process" into a maintainer-screen edit rather than a recurring release. B is the safer choice if the priority is not touching data that already exists in RD/demo.

These two PRs are mutually exclusive — merge one and close the other. Option B: #609

What this PR does

Replaces the four leaves with the four IPPU branches:

Procesos industriales - Minerales (2A)   Cemento · Cal · Vidrio · Cerámica · Otros usos de carbonatos · Otro
Procesos industriales - Química  (2B)    Amoníaco · Ácido nítrico · Ácido adípico · Caprolactama ·
                                         Carburo · TiO₂ · Carbonato de sodio · Petroquímicos ·
                                         Fluoroquímicos · Otro
Procesos industriales - Metales  (2C)    Hierro y acero · Ferroaleaciones · Aluminio · Magnesio ·
                                         Plomo · Cinc · Otro
Procesos industriales - Otros    (2D, 2E, 2G, 2H)  Papel y celulosa · Alimentos y bebidas ·
                                         Electrónica · Lubricantes · Ceras · Solventes · Otro
  • Dimension 1 Proceso is required; dimension 2 Material o método cascades from it via parentValue and is optional, so leaving it blank is itself the escape hatch.
  • Every branch ends in a bare Otro, matching the convention established on feat/mati/machinery-catalog-otro-option (the dimension name supplies the context, and the capture dropdown sorts that exact literal last).
  • Reduction-plan initiatives and sector recommendations are re-keyed onto the branches; the four leaf explanation guides are replaced by one guide per branch.

Emission factors

All values are IPCC 2006 Guidelines, Volume 3 (IPPU) Tier 1 defaults, read from the published PDFs, in kg/ton:

Process Source Values
Cemento §2.2.1.3 clinker 520
Cal Table 2.4 750 · 770 · 590
Vidrio Table 2.6 210 · 190 · 250 · 200 · 100 · 30 · 180 · 130
Hierro y acero Table 4.1 BOF 1460 · EAF 80 · OHF 1720 · global 1060 · sinter 200 · coke 560 · pig iron 1350 · DRI 700 · pellets 30
Ferroaleaciones Table 4.5 2500 · 3600 · 4000 · 4800 · 1300 · 1500 · 1400 · 5000 · 1300
Aluminio Table 4.10 Prebake 1600 · Søderberg 1700
Plomo generic factors by furnace 590 · 250 · 200 · 520
Cinc Tier 1 table Waelz 3660 · ISF 430 · default 1720

Química and Otros seed no defaults, deliberately. The IPCC values there are per-gas (N₂O for nitric and adipic acid, fluorinated gases for 2B9), and collapsing them into a single kgCO₂e figure would bake in a GWP choice that belongs to whoever owns the methodology. Those processes require a custom factor, and the explanation walks through the conversion.

Dolomitic lime uses 0.77, the value IPCC Table 2.4 recommends for developing countries, rather than 0.86.

⚠️ Aluminium seeds only the anode-consumption CO₂ factor, which is all Table 4.10 covers. PFC emissions from the anode effect are excluded. The explanation states this and instructs a separate line, because the seeder cannot express a per-gas split — EmissionFactor.gasDetails exists and is validated against the total, but seedEmissionFactors hardcodes it to {}.

First commit applies to both options

0aacb29 normalizes the four existing subcategories to kg/ton, and it is a behaviour change worth reading closely:

  • Acero and Cinc were understated by 1000×. They carried the IPCC tonne-CO₂-per-tonne figures (BOF 1.46, Waelz 3.66) while declaring the rate unit as kg/ton, so the seeded factor read as 1.46 kg CO₂ per tonne of steel. Corrected to 1460 and 3660.
  • Cemento and Vidrio were numerically right (kg/kg equals t/t) but only offered when declaring in kilograms. The emission editor matches a factor by exact rate-unit denominator and performs no unit conversion, so declaring clinker in tonnes — which the subcategory guide teaches step by step — fell through to a custom factor. Now they resolve in tonnes.
  • Grams are dropped from the allowed units: meaningless at this scale, and they only widened the set of unit choices that resolve to no factor.

Any existing inventory using steel or zinc process emissions will change when reseeded. That is the point, but it should be a conscious call.

Verification

  • pnpm format:check, pnpm lint, pnpm type-check — all clean.
  • Both datasets parse against the real FullMethodologyDataSchema: 23 subcategories, 4 industrial, 79 dimension values, 41 factors, identical in base and testing.
  • Checked against the constraints that only bite at seed time or in the editor: unique (subcategory, code) and (subcategory, position) on dimensions, unique (dimension, value) on values, unique (subcategory, dv1, dv2, source) on factors, every factor's rate-unit denominator present in the subcategory's allowed units, every parentValue and factor reference resolving, and one explanation file per subcategory.
  • Not run: the seed itself against a live database.

Out of scope, found on the way

  1. 25 of 255 seeded emission factors are silently dropped. Combustiones estacionarias (-20) and Combustiones móviles (-5) declare the same fuel three times with the same source and different rate units (kg/kWh, kg/m3, kg/ton). The unique index emission_factor_unique_subcategory_dims_source does not include the rate unit, and createMany runs with skipDuplicates: true, so only the first survives. Declaring diesel in litres or m³ therefore finds no library factor. Same root cause as the cement issue above; worth its own fix.
  2. gasDetails is never seeded. The column supports a per-gas breakdown and is validated against the total, but the seeder writes {}. Populating it would let aluminium carry its CO₂/PFC split and satisfy the per-gas reporting both ISO 14064-1 and the GHG Protocol require.
  3. No new sector recommendations. Chemical subsectors still do not recommend the chemical process subcategory. Deliberately left out so the two PRs differ only in structure; worth a follow-up so the feature is discoverable rather than merely present.
  4. Overlap with refactor/mati/remove-factor-propio-source. That branch relabels the custom factor source to Otro. The explanation guides here say "Factor propio", matching main today — whichever lands second needs a pass over the new guides.

CI

22 of 24 checks pass. Audit and Trivy Image Scan (api) fail on deepmerge-ts 7.1.5 (CVE-2026-40345), a transitive dependency of @prisma/client. Pre-existing on main and unrelated to seed data — already addressed on fix/mati/deepmerge-ts-cve.

The four industrial-process subcategories carried IPCC tonne-CO2-per-tonne-
product figures, but two of them declared the rate unit as kg/ton, which made
the seeded value read as kg CO2 per tonne:

  - Acero: BOF 1.46 kg/ton instead of 1460 (IPCC 2006 Vol.3 Table 4.1)
  - Cinc:  Waelz 3.66 kg/ton instead of 3660 (IPCC 2006 Vol.3 Tier 1 table)

Steel and zinc process emissions were therefore understated by 1000x. Cemento
and Vidrio were numerically correct because kg/kg equals t/t, but they were
only offered when declaring in kilograms — the emission editor matches a factor
by exact rate-unit denominator and performs no unit conversion, so declaring
clinker in tonnes (what the subcategory explanation teaches) fell through to
"Factor Propio".

Normalizing all four to kg/ton fixes the understatement and makes the IPCC
default available in the unit these processes are actually reported in. Grams
are dropped from the allowed units: they are meaningless at this scale and only
widen the set of unit choices that resolve to no factor.

Values verified against the IPCC 2006 Guidelines, Volume 3 (IPPU):
cement section 2.2.1.3, glass Table 2.6, steel Table 4.1, zinc Tier 1 table.
The Alcance 1 catalogue exposed four industrial processes as sibling
subcategories (Cemento, Vidrio, Acero, Cinc). That set is an arbitrary subset of
the IPCC IPPU tree: it includes glass and zinc, which the GHG Protocol's own
sector table does not list, and omits lime, aluminium, chemicals and pulp &
paper, which it does. A company doing anything else - chemicals being the case
that surfaced this - had nowhere to declare it.

Replaces the four leaves with the four IPPU branches, each carrying a two-level
cascade that matches the standard's own hierarchy:

  Procesos industriales - Minerales (2A)  Cemento, Cal, Vidrio, Ceramica, ...
  Procesos industriales - Quimica  (2B)  Amoniaco, Acido nitrico, ...
  Procesos industriales - Metales  (2C)  Hierro y acero, Ferroaleaciones,
                                         Aluminio, Magnesio, Plomo, Cinc, ...
  Procesos industriales - Otros    (2D, 2E, 2G, 2H)  Papel y celulosa, ...

dimension 1 "Proceso" is required, dimension 2 "Material o metodo" cascades from
it via parentValue and is optional. Every branch ends in an explicit residual
value, mirroring the "Other (please specify)" leaf the IPPU tree carries on each
branch; the line comment is where the process gets specified.

Coverage goes from 4 processes to 29, and the subcategory count in Alcance 1
stays at 11 - adding a process is now a dimension value, editable from the
maintainer screens, instead of a new subcategory and a new release.

Emission factors are IPCC 2006 Vol.3 Tier 1 defaults in kg/ton: minerals from
sections 2.2-2.4, steel Table 4.1, ferroalloys Table 4.5, aluminium Table 4.10,
lead and zinc from their Tier 1 tables. Quimica and Otros seed no defaults on
purpose: the IPCC values there are per-gas (N2O for nitric and adipic acid) and
collapsing them into a single kgCO2e figure would bake in a GWP choice that
belongs to whoever owns the methodology. Those processes require "Factor
Propio", and the explanation walks through the conversion.

Aluminium seeds only the anode-consumption CO2 factor, which is what Table 4.10
covers; the explanation states that PFC emissions are excluded and must be
declared separately, since the seed has no way to express a per-gas breakdown
(EmissionFactor.gasDetails exists but the seeder hardcodes it to {}).

Reduction-plan initiatives and sector recommendations are re-keyed onto the
branches, and the four leaf explanations are replaced by one guide per branch.
Each branch's "Proceso" dimension named its residual after the branch ("Otro
proceso mineral", "Otro proceso quimico", ...). The repo convention is a bare
"Otro": the dimension name already supplies the context, and the capture
dropdown sorts that exact literal last via
EMISSION_FACTOR_DIMENSION_OTHER_VALUE, so a longer name would land
alphabetically in the middle of the catalogue instead of at the end.

Only factor-less values are renamed. "Otros usos de carbonatos" stays as it is:
it is a real IPPU 2A4 node, not an escape hatch. The cascading "Material o
metodo" dimension gets none - it is optional, so leaving it blank already is the
escape hatch.
@mrivas00 mrivas00 added type: feature new user-facing capability or enhancement area: database packages/database (Prisma schema / migrations) labels Aug 19, 2026
@mrivas00 mrivas00 closed this Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: database packages/database (Prisma schema / migrations) type: feature new user-facing capability or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant