├── VERSION ├── .gitignore ├── banner_share.jpg ├── jest.config.js ├── src ├── lib │ ├── sorting.ts │ └── errors.ts ├── doc │ ├── testing.md │ ├── data_structure.md │ ├── shoes │ │ └── sneakers │ │ │ └── methodologyDetails.md │ ├── expansion.md │ └── model.md ├── types.ts ├── entities │ ├── ModelParameterEntity.ts │ ├── EmissionFactorEntity.ts │ ├── PartialProductDataEntity.ts │ ├── ProductDataTemplateEntity.ts │ ├── ProductFootprintEntity.ts │ └── ProductDataEntity.ts ├── providers │ ├── product-data-template-provider.ts │ ├── emission-factor-provider.test.ts │ ├── geographical-area.ts │ ├── model-parameter-provider.ts │ └── emission-factor-provider.ts ├── legacy │ └── types.ts ├── __tests │ └── end-to-end-computation.test.ts ├── operations │ ├── get-footprint.ts │ ├── compute-product-footprint.ts │ ├── compute-product-footprint.test.ts │ ├── expand-partial-product-data.ts │ └── expand-partial-product-data.test.ts └── data │ ├── productDataTemplates.ts │ └── parameters.ts ├── package.json ├── README.md ├── tsconfig.json └── LICENSE /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **.DS_Store 3 | -------------------------------------------------------------------------------- /banner_share.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kansoapp/carbonfact-models/HEAD/banner_share.jpg -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /src/lib/sorting.ts: -------------------------------------------------------------------------------- 1 | export function compare(a: T, b: T, order: "asc" | "desc"): number { 2 | if (a < b) return order === "asc" ? -1 : 1; 3 | if (a > b) return order === "asc" ? 1 : -1; 4 | return 0; 5 | } 6 | -------------------------------------------------------------------------------- /src/doc/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | To test the model and prevent regressions: 4 | 5 | - Regularly add new end-to-end tests in `__tests/endToEndComputation.test.ts` 6 | - When changing the model, compute the sum of footprints of all product data submissions and check it is the same as the one with the current version of the model. 7 | -------------------------------------------------------------------------------- /src/doc/data_structure.md: -------------------------------------------------------------------------------- 1 | # Data structure 2 | 3 | ## Data entities 4 | 5 | Must only contain data that is independent from the model or engine implementation details. 6 | 7 | For example, `productCategoryId` and `templateId` are not intrinsec data points for a product, rather metadata we associate to match our model implementation. That's why they're not within the data entity structure but transported along it to provide context. 8 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export const ModelVersion = { 2 | version_0_1_0: "0.1.0", 3 | version_0_2_0: "0.2.0", 4 | version_0_2_1: "0.2.1", 5 | version_0_3_0: "0.3.0", 6 | version_0_3_1: "0.3.1", 7 | version_0_4_0: "0.4.0", 8 | version_0_4_1: "0.4.1", 9 | version_0_4_2: "0.4.2", 10 | version_0_5_0: "0.5.0", 11 | current: "0.5.0", 12 | } as const; 13 | 14 | export type ModelVersion = typeof ModelVersion[keyof typeof ModelVersion]; 15 | -------------------------------------------------------------------------------- /src/entities/ModelParameterEntity.ts: -------------------------------------------------------------------------------- 1 | import { ModelVersion } from "../types"; 2 | 3 | export type Unit = "kgCO2eq" | "kgCO2eq/kg" | "kWh"; 4 | 5 | // TECHNICAL-DEBT some duplication with EmissionFactorEntity 6 | // TODO: add "label" (short, e.g. for dropdowns), "description", "explanation" 7 | export type ModelParameterEntity = { 8 | id: string; // path to identify the model parameter 9 | source: string; // human-readable explanation and sources 10 | description: string; 11 | value: number; 12 | unit: Unit; 13 | countryIds?: string[]; 14 | version: ModelVersion; 15 | comments?: string; 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "carbonfact-models", 3 | "version": "0.2.0", 4 | "description": "Source code used in Carbonfact to calculate the carbon footprints.", 5 | "main": "index.js", 6 | "repository": "git@github.com:kansoapp/carbonfact-models.git", 7 | "author": "Carbonfact - Kanso Inc.", 8 | "license": "MPL-2.0", 9 | "devDependencies": { 10 | "@types/node": "^16.4.10", 11 | "jest": "^27.0.6", 12 | "ts-node": "^10.1.0", 13 | "typescript": "^4.3.5" 14 | }, 15 | "scripts": { 16 | "test": "yarn jest" 17 | }, 18 | "dependencies": { 19 | "@types/jest": "^26.0.24", 20 | "ts-jest": "^27.0.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/entities/EmissionFactorEntity.ts: -------------------------------------------------------------------------------- 1 | import { ModelVersion } from "../types"; 2 | 3 | export type EmissionFactorUnit = "kgCO2eq/kg" | "kgCO2eq/kWh"; 4 | 5 | export type EmissionFactorEntity = { 6 | id: string; // path to identify the entity represented by this parameter (not unique, several emission factors could share an id but differ in country, version...) 7 | label: string; // human-readable string to display the model parameter in the UI (e.g. labels, dropdowns...) 8 | description?: string; // human-readable description of the material (e.g. what it's made of, how...) 9 | source: string; // human-readable explanation and sources 10 | value: number; 11 | unit: EmissionFactorUnit; 12 | countryIds: string[]; 13 | version: ModelVersion; 14 | comments?: string; 15 | }; 16 | -------------------------------------------------------------------------------- /src/entities/PartialProductDataEntity.ts: -------------------------------------------------------------------------------- 1 | import { ProductComponentId } from "./ProductDataEntity"; 2 | 3 | export type PartialMaterialItem = { 4 | materialId: string; 5 | proportion?: number; 6 | }; 7 | 8 | // TECHNICAL-DEBT could probably extract the type instead of building it 9 | // and composing the other one with it. 10 | export type PartialProductDataEntityComponent = { 11 | componentId: ProductComponentId; 12 | materials?: PartialMaterialItem[]; 13 | materialCountryId?: string; 14 | proportion?: number; 15 | }; 16 | 17 | // TECHNICAL-DEBT could use TS's type utility 18 | export interface PartialProductDataEntity { 19 | weight?: number; 20 | components?: PartialProductDataEntityComponent[]; 21 | manufacturingCountryId?: string; 22 | distributionMode?: string; 23 | endOfLifeRecyclingProgram?: boolean; 24 | endOfLifeOption?: string; 25 | endOfLifeValue?: number | null; 26 | } 27 | -------------------------------------------------------------------------------- /src/entities/ProductDataTemplateEntity.ts: -------------------------------------------------------------------------------- 1 | import { ModelVersion } from "../types"; 2 | import { ProductDataEntity } from "./ProductDataEntity"; 3 | 4 | export interface ProductDataTemplateEntity 5 | extends Omit { 6 | // See doc/expansion.md to know why "distributionMode" is omitted. 7 | 8 | id: string; // referenced by ProductDataSubmission.templateId 9 | label: string; // to be displayed in UI 10 | source: string; // explains how the template was constructed and/or gives the sources 11 | productCategorySlug: string; // uniquely identifies the product category this template applies to 12 | 13 | /** 14 | * The version of the model this parameter is associated with. 15 | * A template should be selected so that it's `modelVersion` is 16 | * the maximum that is less than or equal to the version of the model 17 | * being calculated. 18 | * 19 | * For example: 20 | * - We want to calculate for model v0.2.1. 21 | * - The template exists in versions v0.1.0, v0.2.0, v0.3.0. 22 | * - The version v0.2.0 should be selected. 23 | */ 24 | modelVersion: ModelVersion; 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/errors.ts: -------------------------------------------------------------------------------- 1 | export class NotFoundError extends Error { 2 | constructor(message: string) { 3 | super(message); 4 | this.name = "NotFoundError"; 5 | } 6 | } 7 | 8 | export class AuthenticationError extends Error { 9 | constructor(message: string) { 10 | super(message); 11 | this.name = "AuthenticationError"; 12 | } 13 | } 14 | 15 | export class InvalidRequestError extends Error { 16 | constructor(message: string) { 17 | super(message); 18 | this.name = "InvalidRequestError"; 19 | } 20 | } 21 | 22 | export class MissingEmissionFactorMappingError extends Error { 23 | materialId: string; 24 | countryId: string; 25 | modelVersion: string; 26 | 27 | constructor(materialId: string, countryId: string, modelVersion: string) { 28 | super( 29 | `Missing emission factor for id "${materialId}", country "${countryId}", model version "${modelVersion}"` 30 | ); 31 | 32 | // Necessary for Typescript 33 | Object.setPrototypeOf(this, MissingEmissionFactorMappingError.prototype); 34 | 35 | this.materialId = materialId; 36 | this.countryId = countryId; 37 | this.modelVersion = modelVersion; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/entities/ProductFootprintEntity.ts: -------------------------------------------------------------------------------- 1 | import { EmissionFactorEntity } from "./EmissionFactorEntity"; 2 | import { ModelParameterEntity, Unit } from "./ModelParameterEntity"; 3 | import { ProductComponentId } from "./ProductDataEntity"; 4 | 5 | export type ProductFootprintEntity = { 6 | total: number; 7 | breakdown: { 8 | materials: number; 9 | manufacturing: number; 10 | distribution: number; 11 | use: number; 12 | endOfLife: number; 13 | }; 14 | explanation?: ProductFootprintExplanation; 15 | }; 16 | 17 | export type ProductFootprintExplanation = { 18 | materials: { 19 | components: { 20 | componentId: ProductComponentId; 21 | materials: { 22 | materialId: string; 23 | proportion: number; 24 | emissionFactor: EmissionFactorEntity; 25 | humanReadable: string; 26 | }[]; 27 | proportion: number; 28 | total: number; 29 | unit: Unit; 30 | }[]; 31 | humanReadable: string; 32 | }; 33 | manufacturing: { 34 | humanReadable: string; 35 | }; 36 | distribution: { 37 | modelParameter: ModelParameterEntity; 38 | humanReadable: string; 39 | }; 40 | use: { 41 | humanReadable: string; 42 | }; 43 | endOfLife: { 44 | modelParameter?: ModelParameterEntity; 45 | humanReadable: string; 46 | }; 47 | total: { 48 | humanReadable: string; 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /src/providers/product-data-template-provider.ts: -------------------------------------------------------------------------------- 1 | import { compare } from "../lib/sorting"; 2 | import { productDataTemplates } from "../data/productDataTemplates"; 3 | import { ModelVersion } from "../types"; 4 | import { ProductDataTemplateEntity } from "../entities/ProductDataTemplateEntity"; 5 | 6 | export const defaultTemplateId = "shoes/sneakers/generic"; 7 | 8 | export interface IProductDataTemplateProvider { 9 | get: (id: string, modelVersion: ModelVersion) => ProductDataTemplateEntity; 10 | allIdAndLabel: ( 11 | modelVersion: ModelVersion 12 | ) => { id: string; label: string }[]; 13 | } 14 | 15 | export function buildProductDataTemplateProvider(): IProductDataTemplateProvider { 16 | return { 17 | get: (id: string, modelVersion: ModelVersion) => { 18 | const matchingTemplates = productDataTemplates.filter( 19 | (pt) => pt.id === id && pt.modelVersion <= modelVersion 20 | ); 21 | if (matchingTemplates.length === 0) { 22 | throw new Error( 23 | `no template with id "${id}" and model <= ${modelVersion}` 24 | ); 25 | } 26 | matchingTemplates.sort((t1, t2) => 27 | compare(t1.modelVersion, t2.modelVersion, "desc") 28 | ); 29 | return matchingTemplates[0]; 30 | }, 31 | 32 | allIdAndLabel: (modelVersion: ModelVersion) => { 33 | return productDataTemplates.map((t) => ({ id: t.id, label: t.label })); 34 | }, 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/entities/ProductDataEntity.ts: -------------------------------------------------------------------------------- 1 | export const ProductComponentId = { 2 | insole: "insole", 3 | outsole: "outsole", 4 | lining: "lining", 5 | laces: "laces", 6 | upper: "upper", 7 | sides: "sides", 8 | other: "other", 9 | } as const; 10 | 11 | export type ProductComponentId = 12 | typeof ProductComponentId[keyof typeof ProductComponentId]; 13 | 14 | // TECHNICAL-DEBT type duplication with PartialProductDataEntity 15 | // maybe could DRY it using type composition. 16 | 17 | /** 18 | * ProductDataEntity represents the data used to perform the 19 | * footprint computation. 20 | * 21 | * ### Rules 22 | * 23 | * - No optional value: if optional values are needed, `PartialProductDataEntity` 24 | * must be used instead. Prior to perform the computation, 25 | * `ExpandPartialProductData` operation must be used to expand the data 26 | * into a `ProductDataEntity`. 27 | */ 28 | 29 | export type MaterialItem = { 30 | materialId: string; 31 | proportion: number; 32 | }; 33 | 34 | export type ProductDataEntityComponent = { 35 | componentId: ProductComponentId; 36 | materials: MaterialItem[]; 37 | materialCountryId: string; 38 | proportion: number; 39 | }; 40 | 41 | export type ProductDataEntity = { 42 | weight: number; 43 | components: ProductDataEntityComponent[]; 44 | manufacturingCountryId: string; 45 | distributionMode: string; 46 | 47 | // End-of-life 48 | endOfLifeRecyclingProgram?: boolean; // DEPRECATED, replaced by endOfLifeOption and endOfLifeValue 49 | 50 | endOfLifeOption: string | "custom"; 51 | // An option to select the model parameter to apply for the EOL emissions. 52 | // This should match model parameters with path starting with 53 | // `fixedValue/lifeCycleAnalysisStep/endOfLife/`, e.g. 54 | // `withoutRecyclingProgram`. 55 | // If "custom" is used, then `endOfLifeCustomValue` must be not null. 56 | 57 | endOfLifeValue: number | null; 58 | // A custom value for the EOL emissions (in kgCO2eq) to be set when 59 | // `endOfLifeOption` is "custom". 60 | }; 61 | -------------------------------------------------------------------------------- /src/legacy/types.ts: -------------------------------------------------------------------------------- 1 | import { ProductComponentId } from "../entities/ProductDataEntity"; 2 | import { ModelVersion } from "../types"; 3 | 4 | export interface ModelParameter { 5 | id: string; // a path to identify the entity represented by this parameter 6 | label: string; // a human-readable string to display the model parameter in the UI 7 | description?: string; 8 | source: string; // human-readable explanation and sources 9 | value: number; 10 | variationCoefficient?: number; 11 | unit: "kgCO2eq" | "kgCO2eq/kg" | "kWh" | "kgCO2eq/kWh"; 12 | countryIds?: string[]; 13 | comments?: string; 14 | version: ModelVersion; 15 | } 16 | 17 | export interface ProductDataComponent { 18 | componentId: ProductComponentId; 19 | materialId: string; 20 | materialSourceUrls: string[]; 21 | proportion: number; 22 | proportionSourceUrls: string[]; 23 | } 24 | 25 | export interface ProductData { 26 | modelVersion: string; 27 | weight: number; 28 | weightSourceUrls: string[]; 29 | components: ProductDataComponent[]; 30 | manufacturingCountry: string; 31 | manufacturingCountrySourceUrls: string[]; 32 | endOfLifeRecyclingProgram: boolean; 33 | endOfLifeRecyclingProgramSourceUrls: string[]; 34 | } 35 | 36 | export interface ProductDataTemplate extends ProductData { 37 | id: string; // referenced by ProductDataPartial.templateId 38 | label: string; // to be displayed in UI 39 | source: string; // explains how the template was constructed and/or gives the sources 40 | } 41 | 42 | export interface ProductDataComponentPartial { 43 | componentId: ProductComponentId; 44 | materialId: string; 45 | materialSourceUrls: string[]; 46 | proportion?: number; 47 | proportionSourceUrls?: string[]; 48 | } 49 | 50 | export interface ProductDataPartial { 51 | templateId: string; 52 | modelVersion: string; // TECHNICAL-DEBT: should be ModelVersion 53 | weight?: number; 54 | weightSourceUrls?: string[]; 55 | components?: ProductDataComponentPartial[]; 56 | manufacturingCountry?: string; 57 | manufacturingCountrySourceUrls?: string[]; 58 | endOfLifeRecyclingProgram?: boolean; 59 | endOfLifeRecyclingProgramSourceUrls?: string[]; 60 | } 61 | -------------------------------------------------------------------------------- /src/providers/emission-factor-provider.test.ts: -------------------------------------------------------------------------------- 1 | import { EmissionFactorEntity } from "../entities/EmissionFactorEntity"; 2 | import { ModelVersion } from "../types"; 3 | import { 4 | buildEmissionFactorProvider, 5 | EmissionFactorProvider, 6 | } from "./emission-factor-provider"; 7 | 8 | const modelVersion = ModelVersion.current; 9 | const emissionFactorProvider: EmissionFactorProvider = 10 | buildEmissionFactorProvider(); 11 | 12 | it("returns the correct value for China's electricity emission factor", () => { 13 | const ef = emissionFactorProvider.get( 14 | "energy/electricity", 15 | modelVersion, 16 | "china" 17 | ); 18 | expect(ef.value).toBeCloseTo(0.555); 19 | expect(ef.unit).toBe("kgCO2eq/kWh"); 20 | expect(ef.countryIds).toStrictEqual(["china"]); 21 | }); 22 | 23 | it("returns the correct value for an older version", () => { 24 | const ef = emissionFactorProvider.get( 25 | "energy/electricity", 26 | ModelVersion.version_0_2_0, 27 | "china" 28 | ); 29 | expect(ef.value).toBeCloseTo(0.6); 30 | expect(ef.unit).toBe("kgCO2eq/kWh"); 31 | expect(ef.countryIds).toStrictEqual(["china"]); 32 | }); 33 | 34 | it("throws an error when no emission factor matching the id is found", () => { 35 | expect(() => 36 | emissionFactorProvider.get("unknown", modelVersion, "china") 37 | ).toThrowError(); 38 | }); 39 | 40 | it("throws an error when no emission factor matching the country is found", () => { 41 | expect(() => 42 | emissionFactorProvider.get("energy/electricity", modelVersion, "bhoutan") 43 | ).toThrowError(); 44 | }); 45 | 46 | // Disabled this test since in the `0.5.0` update we only 47 | // kept the latest version of the emission factor, so it 48 | // cannot be tested anymore. 49 | // it("returns the value for the maximum version lower or equal to the specified one when there are several matches for id, version and country", () => { 50 | // let ef: EmissionFactorEntity; 51 | // ef = emissionFactorProvider.get( 52 | // "material/polyester/standard", 53 | // ModelVersion.version_0_2_0, 54 | // "portugal" 55 | // ); 56 | // expect(ef.value).toBeCloseTo(4.561); 57 | 58 | // ef = emissionFactorProvider.get( 59 | // "material/polyester/standard", 60 | // modelVersion, 61 | // "portugal" 62 | // ); 63 | // expect(ef.value).toBeCloseTo(8.558); 64 | // }); 65 | 66 | it("throws an error when there is no match on id and country below the specified version", () => { 67 | expect(() => 68 | emissionFactorProvider.get( 69 | "material/rubber/synthetic", 70 | ModelVersion.version_0_1_0, 71 | "italy" 72 | ) 73 | ).toThrowError(); 74 | }); 75 | -------------------------------------------------------------------------------- /src/providers/geographical-area.ts: -------------------------------------------------------------------------------- 1 | type GeographicalAreaBase = { 2 | id: string; 3 | label: string; 4 | }; 5 | type Continent = GeographicalAreaBase & { 6 | type: "continent"; 7 | }; 8 | type Country = GeographicalAreaBase & { 9 | type: "country"; 10 | parentId: string; 11 | }; 12 | export type GeographicalArea = Continent | Country; 13 | 14 | export interface IGeographicalAreaProvider { 15 | /** 16 | * Returns the GeographicalAreaEntity matching the specified id. 17 | * 18 | * If there is no match, throws an Error. 19 | */ 20 | getById: (id: string) => GeographicalArea; 21 | 22 | /** 23 | * Returns all countries. 24 | */ 25 | allCountries: () => GeographicalArea[]; 26 | } 27 | 28 | export const continents: { [key: string]: GeographicalArea } = { 29 | asia: { id: "asia", label: "Asia", type: "continent" }, 30 | europe: { 31 | type: "continent", 32 | id: "europe", 33 | label: "Europe", 34 | }, 35 | southAmerica: { 36 | type: "continent", 37 | id: "southAmerica", 38 | label: "South America", 39 | }, 40 | africa: { 41 | type: "continent", 42 | id: "africa", 43 | label: "Africa", 44 | }, 45 | }; 46 | 47 | export const countries: { [id: string]: Country } = { 48 | china: { 49 | type: "country", 50 | id: "china", 51 | label: "China", 52 | parentId: "asia", 53 | }, 54 | vietnam: { 55 | type: "country", 56 | id: "vietnam", 57 | label: "Vietnam", 58 | parentId: "asia", 59 | }, 60 | italy: { type: "country", id: "italy", label: "Italy", parentId: "europe" }, 61 | spain: { type: "country", id: "spain", label: "Spain", parentId: "europe" }, 62 | portugal: { 63 | type: "country", 64 | id: "portugal", 65 | label: "Portugal", 66 | parentId: "europe", 67 | }, 68 | greece: { 69 | type: "country", 70 | id: "greece", 71 | label: "Greece", 72 | parentId: "europe", 73 | }, 74 | peru: { 75 | type: "country", 76 | id: "peru", 77 | label: "Peru", 78 | parentId: "southAmerica", 79 | }, 80 | france: { 81 | type: "country", 82 | id: "france", 83 | label: "France", 84 | parentId: "europe", 85 | }, 86 | senegal: { 87 | type: "country", 88 | id: "senegal", 89 | label: "Senegal", 90 | parentId: "africa", 91 | }, 92 | }; 93 | 94 | export const buildGeographicalAreaProvider: () => IGeographicalAreaProvider = 95 | () => { 96 | return { 97 | getById: (id: string) => { 98 | return countries[id] || continents[id]; 99 | }, 100 | 101 | allCountries: () => { 102 | return Object.values(countries); 103 | }, 104 | }; 105 | }; 106 | -------------------------------------------------------------------------------- /src/doc/shoes/sneakers/methodologyDetails.md: -------------------------------------------------------------------------------- 1 | # Shoes > Sneakers > Methodology details 2 | 3 | ## LCA breakdowns and weight in several sources 4 | 5 | Let's review the different steps used in several life-cycle analyses for shoes and how much they account in the total footprint in several references. 6 | 7 | ### ADEME BaseImpact 8 | 9 | According to ADEME BaseImpact LCA for a reference "fabric shoes" product model. 10 | 11 | NB: we refer to [this document](https://docs.google.com/spreadsheets/d/1dV_kZYekUy-h6shJHynczlLRaqFaK2mQ/edit#gid=1913794051) we downloaded from ADEME BaseImpact website and uploaded to our Google Drive for easier use and sharing. 12 | 13 | - Raw materials: 26.0% 14 | - Raw materials transport: 1.6% 15 | - Formatting: 21.5% 16 | - Assembling and distribution: 44.4% 17 | - Use: 0.0% 18 | - End-of-life: 6.6% 19 | 20 | In more details: 21 | 22 | - Raw materials: 23 | - Production of raw materials: 26% 24 | - Raw materials transport: 25 | - Truck: 1.6% 26 | - Train: 0.0% 27 | - Boat: 0.0% 28 | - Plane: 0.0% 29 | - Formatting: 30 | - Formatting steps: 19.4% 31 | - Intermediate processes: 0.0% 32 | - Intermediate transport: 2.1% 33 | - Assembling and distribution: 34 | - Assembling: 23.3% 35 | - Distribution: 21.1% 36 | - Storage in warehouses: 0.0% 37 | - Storage in retail stores: 0.0% 38 | - Use: 39 | - Products: 0.0% 40 | - Energy: 0.0% 41 | - End-of-life: 42 | - Production losses: 1.0% 43 | - Collecting and sorting: 0.1% 44 | - Disposal: 5.5% 45 | 46 | ### MIT Manufacturing-Focused Emissions Reductions in Footwear Production 47 | 48 | [Source](https://dspace.mit.edu/bitstream/handle/1721.1/102070/Olivetti_Manufacturing-focused.pdf?sequence=1&isAllowed=y) 49 | 50 | Total footprint: 14 ± 2.7 kgCO2eq 51 | 52 | - Materials: 4.0 ± 0.36 kgCO2eq (28.6 %) 53 | - Manufacturing: 9.7 ± 2.7 kgCO2eq (69.3 %) 54 | - Transport: 0.25 kgCO2eq - data not available, approximed from a chart (< 2 %) 55 | - Use: neglible (0.0 %) 56 | - End-of-life: 0.25 kgCO2eq - data not available, approximed from a chart (< 2 %) 57 | 58 | ## Carbonfact's LCA breadown 59 | 60 | We use the following steps and this is how they map to the referenced breakdowns: 61 | 62 | - Materials: 54 % -> included 63 | - ADEME: Raw materials + Formatting steps (45.4 %) 64 | - MIT: Materials + part of Manufacturing - we take half (63.2 %) 65 | - Assembling: 29 % -> included 66 | - ADEME: Assembling (23 %) 67 | - MIT: part of Manufacturing - we take the other half (34.6 %) 68 | - Upstream transport: 2 % -> not included 69 | - ADEME: Raw materials transport + Intermediate transport (3.7 %) 70 | - MIT: negligible part of Transport (0.034 kgCO2eq / pair of shoes, < 0.03 %) 71 | - Distribution: 11 % -> included 72 | - ADEME: Distribution (21.1 %) 73 | - MIT: most part of Transport (0.24 kgCO2eq, 1.7 %) 74 | - Use: 0% -> not included 75 | - ADEME: Use (0.0%) 76 | - MIT: negligible 77 | - End-of-life: 4 % -> included 78 | - ADEME: End-of-life (6.6 %) 79 | - MIT: Use (< 2 %) 80 | 81 | The 2 references yield different weights for the different steps. We average both in our own breakdown to identify the most steps on which we must make our model the most accurate possible: 82 | 83 | - Materials 84 | - Assembling 85 | - Distribution 86 | -------------------------------------------------------------------------------- /src/providers/model-parameter-provider.ts: -------------------------------------------------------------------------------- 1 | import { compare } from "../lib/sorting"; 2 | import { modelParameters as allModelParameters } from "../data/parameters"; 3 | import { ModelParameterEntity, Unit } from "../entities/ModelParameterEntity"; 4 | import { ModelParameter } from "../legacy/types"; 5 | import { ModelVersion } from "../types"; 6 | 7 | const modelParameters = allModelParameters.filter((mp) => 8 | mp.id.startsWith("fixedValue/") 9 | ); 10 | 11 | export interface ModelParameterProvider { 12 | /** 13 | * Returns the model parameters for the given id and model version. 14 | * 15 | * Rules 16 | * ----- 17 | * - If `countryId` is provided, the model parameter will be searched within 18 | * the ones connected to this country. 19 | * - If `countryId` is not provided, all model parameters matching this id 20 | * must not be associated to any country. Otherwise, an error is thrown. 21 | */ 22 | get: ( 23 | id: string, 24 | version: ModelVersion, 25 | countryId?: string 26 | ) => ModelParameterEntity; 27 | } 28 | 29 | export function buildModelParameterProvider(): ModelParameterProvider { 30 | return { 31 | get: (id: string, version: ModelVersion, countryId?: string) => { 32 | // TECHNICAL-DEBT: duplication with EmissionFactorProvider 33 | 34 | let selectedModelParameter: ModelParameter; 35 | const inVersionRange = modelParameters.filter( 36 | (mp) => mp.id === `fixedValue/${id}` && mp.version <= version 37 | ); 38 | if (countryId) { 39 | const inVersionRangeAndMatchingCountry = inVersionRange.filter((mp) => 40 | mp.countryIds?.includes(countryId) 41 | ); 42 | if (inVersionRangeAndMatchingCountry.length === 0) { 43 | throw new Error( 44 | `ModelParameter "${id}" for ${countryId}, ${version} not found` 45 | ); 46 | } 47 | inVersionRangeAndMatchingCountry.sort((mp1, mp2) => 48 | compare(mp1.version, mp2.version, "desc") 49 | ); 50 | selectedModelParameter = inVersionRangeAndMatchingCountry[0]; 51 | } else { 52 | // No `countryId` provided, matching model parameters must not be associated to any country. 53 | if ( 54 | inVersionRange.filter( 55 | (mp) => mp.countryIds && mp.countryIds.length > 0 56 | ).length > 0 57 | ) { 58 | throw new Error( 59 | `unexpected error: found ModelParameter "${id}" with countryIds while requesting without 'countryId' param` 60 | ); 61 | } 62 | inVersionRange.sort((mp1, mp2) => 63 | compare(mp1.version, mp2.version, "desc") 64 | ); 65 | selectedModelParameter = inVersionRange[0]; 66 | } 67 | 68 | const modelParameter: ModelParameterEntity = { 69 | id, 70 | source: selectedModelParameter.source, 71 | description: selectedModelParameter.label, // TECHNICAL-DEBT: should be description 72 | value: selectedModelParameter.value, 73 | unit: selectedModelParameter.unit as Unit, 74 | countryIds: selectedModelParameter.countryIds, 75 | version: selectedModelParameter.version, 76 | comments: selectedModelParameter.comments, 77 | }; 78 | return modelParameter; 79 | }, 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/__tests/end-to-end-computation.test.ts: -------------------------------------------------------------------------------- 1 | import { buildGetFootprintOperation } from "../operations/get-footprint"; 2 | import { PartialProductDataEntity } from "../entities/PartialProductDataEntity"; 3 | import { defaultTemplateId } from "../providers/product-data-template-provider"; 4 | import { ModelVersion } from "../types"; 5 | 6 | /** 7 | * This tests the computation from end to end, using the real 8 | * ModelParameter and EmissionFactor providers. 9 | */ 10 | const getFootprintOperation = buildGetFootprintOperation(); 11 | 12 | /** 13 | * This tests the computation from end-to-end for an older version of the model 14 | * still works and the result is not changed. 15 | * 16 | * This is necessary for now because product page displays the computed footprint 17 | * using the model version specified in the latest data submission, so the engine 18 | * must still be able to compute it correctly. 19 | * 20 | * This may be a decision that changes in the future, were we can decide to roll-out 21 | * a new version of the model and ensure all footprints will be computed with this 22 | * version. 23 | */ 24 | test("Nike Tanjun with model 0.5.0 is correct", () => { 25 | const partial: PartialProductDataEntity = { 26 | components: [ 27 | { 28 | componentId: "upper", 29 | materials: [ 30 | { 31 | materialId: "nylon/standard", 32 | proportion: 1.0, 33 | }, 34 | ], 35 | }, 36 | { 37 | componentId: "outsole", 38 | materials: [ 39 | { 40 | materialId: "eva/standard", 41 | proportion: 1.0, 42 | }, 43 | ], 44 | }, 45 | ], 46 | manufacturingCountryId: "china", 47 | endOfLifeRecyclingProgram: false, 48 | }; 49 | const footprint = getFootprintOperation.forPartialProductData( 50 | partial, 51 | defaultTemplateId, 52 | ModelVersion.version_0_5_0 53 | ); 54 | expect(footprint.breakdown.materials).toBeCloseTo(23.977); 55 | expect(footprint.breakdown.manufacturing).toBeCloseTo(3.33); 56 | expect(footprint.breakdown.distribution).toBeCloseTo(1.053); 57 | expect(footprint.breakdown.use).toBeCloseTo(0); 58 | expect(footprint.breakdown.endOfLife).toBeCloseTo(1.4); 59 | }); 60 | 61 | test("Adidas UltraBoost 21 with model 0.5.0 is correct", () => { 62 | const partial: PartialProductDataEntity = { 63 | weight: 0.71, 64 | components: [ 65 | { 66 | componentId: "upper", 67 | materials: [ 68 | { 69 | materialId: "polyester/standard", 70 | proportion: 1.0, 71 | }, 72 | ], 73 | proportion: (0.047 * 2) / 0.71, // formula in LCA shoes Google Sheet 74 | }, 75 | { 76 | componentId: "outsole", 77 | materials: [ 78 | { 79 | materialId: "rubber/synthetic", 80 | proportion: 1.0, 81 | }, 82 | ], 83 | proportion: (0.246 * 2) / 0.71, // formula in LCA shoes Google Sheet 84 | }, 85 | ], 86 | manufacturingCountryId: "vietnam", 87 | endOfLifeRecyclingProgram: false, 88 | }; 89 | const footprint = getFootprintOperation.forPartialProductData( 90 | partial, 91 | defaultTemplateId, 92 | ModelVersion.version_0_5_0 93 | ); 94 | expect(footprint.breakdown.materials).toBeCloseTo(5.83985); 95 | expect(footprint.breakdown.manufacturing).toBeCloseTo(4.892); 96 | expect(footprint.breakdown.distribution).toBeCloseTo(1.053); 97 | expect(footprint.breakdown.use).toBeCloseTo(0); 98 | expect(footprint.breakdown.endOfLife).toBeCloseTo(1.4); 99 | }); 100 | -------------------------------------------------------------------------------- /src/operations/get-footprint.ts: -------------------------------------------------------------------------------- 1 | import { PartialProductDataEntity } from "../entities/PartialProductDataEntity"; 2 | import { ComputeProductFootprintOperation } from "../operations/compute-product-footprint"; 3 | import { ExpandPartialProductDataEntityOperation } from "../operations/expand-partial-product-data"; 4 | import { ModelVersion } from "../types"; 5 | import { ProductFootprintEntity } from "../entities/ProductFootprintEntity"; 6 | import { 7 | buildGeographicalAreaProvider, 8 | IGeographicalAreaProvider, 9 | } from "../providers/geographical-area"; 10 | import { 11 | buildEmissionFactorProvider, 12 | EmissionFactorProvider, 13 | } from "../providers/emission-factor-provider"; 14 | import { 15 | buildModelParameterProvider, 16 | ModelParameterProvider, 17 | } from "../providers/model-parameter-provider"; 18 | import { 19 | buildProductDataTemplateProvider, 20 | IProductDataTemplateProvider, 21 | } from "../providers/product-data-template-provider"; 22 | import { ProductDataEntity } from "../entities/ProductDataEntity"; 23 | 24 | export interface IGetFootprintOperation { 25 | forProductData: ( 26 | productData: ProductDataEntity, 27 | modelVersion?: ModelVersion 28 | ) => ProductFootprintEntity; 29 | 30 | forPartialProductData: ( 31 | partial: PartialProductDataEntity, 32 | templateId: string, 33 | modelVersion?: ModelVersion 34 | ) => ProductFootprintEntity; 35 | 36 | forProductCategory: ( 37 | categorySlug: string, 38 | modelVersion?: ModelVersion 39 | ) => ProductFootprintEntity; 40 | } 41 | 42 | const defaultEmissionFactorProvider: EmissionFactorProvider = 43 | buildEmissionFactorProvider(); 44 | const defaultModelParameterProvider: ModelParameterProvider = 45 | buildModelParameterProvider(); 46 | const defaultProductDataTemplateProvider: IProductDataTemplateProvider = 47 | buildProductDataTemplateProvider(); 48 | const defaultGeographicalAreaProvider: IGeographicalAreaProvider = 49 | buildGeographicalAreaProvider(); 50 | 51 | export function buildGetFootprintOperation( 52 | emissionFactorProvider: EmissionFactorProvider = defaultEmissionFactorProvider, 53 | modelParameterProvider: ModelParameterProvider = defaultModelParameterProvider, 54 | productDataTemplateProvider: IProductDataTemplateProvider = defaultProductDataTemplateProvider, 55 | geographicalAreaProvider: IGeographicalAreaProvider = defaultGeographicalAreaProvider 56 | ): IGetFootprintOperation { 57 | const compute = ComputeProductFootprintOperation( 58 | emissionFactorProvider, 59 | modelParameterProvider 60 | ); 61 | const expand = ExpandPartialProductDataEntityOperation( 62 | productDataTemplateProvider, 63 | geographicalAreaProvider 64 | ); 65 | 66 | return { 67 | forProductData: ( 68 | productData: ProductDataEntity, 69 | modelVersion: ModelVersion = ModelVersion.current 70 | ) => { 71 | return compute(productData, modelVersion); 72 | }, 73 | 74 | forPartialProductData: ( 75 | partialData: PartialProductDataEntity, 76 | templateId: string, 77 | modelVersion: ModelVersion = ModelVersion.current 78 | ) => { 79 | const data = expand(partialData, templateId, modelVersion); 80 | return compute(data, modelVersion); 81 | }, 82 | 83 | forProductCategory: ( 84 | categorySlug: string, 85 | modelVersion: ModelVersion = ModelVersion.current 86 | ) => { 87 | const categoryTemplateId = (() => { 88 | switch (categorySlug) { 89 | case "shoes": 90 | case "sneakers": 91 | return "shoes/sneakers/generic"; 92 | default: 93 | return `shoes/sneakers/${categorySlug}`; 94 | } 95 | })(); 96 | const data = expand({}, categoryTemplateId, modelVersion); 97 | return compute(data, modelVersion); 98 | }, 99 | }; 100 | } 101 | -------------------------------------------------------------------------------- /src/doc/expansion.md: -------------------------------------------------------------------------------- 1 | # Expansion 2 | 3 | Product data may be incomplete. This is supported by the `PartialpartialDataEntity` data structure, whose most properties are optional. 4 | 5 | To compute the carbon footprint, all data points must be present (`partialDataEntity` has no optional fields). A partial product data may be completed using `ExpandPartialpartialData` operation. 6 | 7 | Two different processes will complete missing data: 8 | 9 | - expanding implicit missing values (e.g. using the manufacturing country for the material's country when not specified), 10 | - expanding using a product template (e.g. determining the components' proportion based on the average product in the category), 11 | - expanding with default values (e.g. completing the unaccounted for proportion of the product's mass with a default placeholder material). 12 | 13 | ## Algorithm 14 | 15 | _NB: the order is important!_ 16 | 17 | ### 1. Expand missing material country with manufacturing country 18 | 19 | **Rule: after expansion, all `partialData.component[n].materialCountryId` fields have a non-blank value** 20 | 21 | For each component, the material may be originating from a specific country. If no specific country is specified, the manufacturing country (`partialData.manufacturingCountryId`) is used. 22 | 23 | **Nota bene** 24 | 25 | - Since this is the first step, this means the template expansion (see below) will not replace a material country id expanded from the manufacturing country. 26 | - If the partial data has no manufacturing country yet, the template's value is used. 27 | 28 | ### 2. Expanding from a template 29 | 30 | Some other values are unknown for a given product because there is not enough data currently available. To complete these values, we use templates that we build to represent at best a standard model for the particular product category. 31 | 32 | During expansion, any value missing from the product's data is then filled with the template's data. 33 | 34 | For components: 35 | 36 | - any component missing in the partial and present in the template is added using the value in the template, 37 | - if `components.materials` is already present in the partial, it's left untouched (since it's already more accurate data than the template's), so it may remain missing values. 38 | 39 | ### 3. Expanding missing material proportions 40 | 41 | Applies to: `partialData.components[n].materials[p].proportion` 42 | 43 | **Rule: after expansion, `sum(partialData.components[n].materials[p].proportion, p in 0..materials.length-1)` is `0.0` or `1.0`** 44 | 45 | If there is no information on the materials of a given component (sum is 0), it's left unchanged so that the data is completed by the template. 46 | 47 | If the information is already complete (sum is 1), there is no change either. 48 | 49 | Otherwise, for materials in a given component whose proportion is undefined, proportion is set to the same value so that the sum of all proportions is 1.0. 50 | 51 | Examples: 52 | 53 | - Component with materialA 30%, materialB ?, materialC ?: B and C proportions are set to 35% (100% - 30%) / 2. 54 | - Component with materialA ?: A proportion is set to 100%. 55 | - Component with materialA ?, materialB ?: A and B are set to 50%. 56 | 57 | If all specified materials have proportion but the sum is not 1.0, the expansion adds a default material to complete (the default material is based on the product category, the component, etc.). 58 | 59 | ### 4. Expanding components with an "other" component for the remaining proportion 60 | 61 | **Rule: after expansion, the sum of `partialData.components[n].proportion` is `1.0`** 62 | 63 | If the sum of proportions of components is not 1.0, an "Other" component is added with a default emission factor and the remaining unidentified proportion. 64 | 65 | ### 5. Expanding `distributionMode` 66 | 67 | **Rules** 68 | 69 | - If present: leave unchanged. 70 | - If missing: 71 | - If the manufacturing country is in Asia: set to `intercontinental/default`. 72 | - Otherwise: set to `intracontinental/default`. 73 | 74 | NB: the `distributionMode` is voluntarily removed from the template so there is no risk of conflict with this business rule. 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Carbonfact Models 2 | 3 | ![](./banner_share.jpg) 4 | 5 | This repository contains the carbon footprint models used by [Carbonfact](https://www.carbonfact.com). 6 | 7 | ## Disclaimer 8 | 9 | This repository is in its early days. Please be indulgent, we're progressively improving it to make it easier to understand, re-use and contribute to. 10 | 11 | If you have questions, please have a look at [the main website](https://www.carbonfact.com) and contact us at [hello@carbonfact.com](mailto:hello@carbonfact.com). 12 | 13 | ## How to use 14 | 15 | This repository is intended for: 16 | 17 | - Explaining how the carbon footprint calculation is done on [Carbonfact](https://www.carbonfact.com). 18 | - Enabling anyone to audit or reuse our methodology. 19 | 20 | For now, we are sharing the source code used on our platform to calculate the carbon footprints. We share it as open-source using a [simple copyleft license](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) so that it may be reused but any improvement to the model must be shared with the community (by us and others). 21 | 22 | To be able to understand how the model works, you will need basic knowledge in software programming and the Typescript language. (We intend to make this easier to understand for non-programmers in the future.) 23 | 24 | **Tests** 25 | 26 | Test files have been added (ending with `.test.js`). You can start by looking at them to understand how to use the different parts of the source code. 27 | 28 | **Run locally** 29 | 30 | ```sh 31 | git clone https://github.com/kansoapp/carbonfact-models 32 | cd carbonfact-models 33 | yarn install 34 | yarn test 35 | ``` 36 | 37 | ## How it works 38 | 39 | **[Compute](./src/operations/ComputeProductFootprint.ts)** 40 | 41 | - The carbon footprint is calculated in here, by the `computeFootprint(productData: ProductData)` method. 42 | - Different types (e.g. `ProductDataEntity`, `EmissionFactorEntity`...) represent the data necessary to perform the computation. They are defined in [src/entities](./src/entities). 43 | 44 | **[Expand](./src/operations/ExpandPartialProductData.ts)** 45 | 46 | _Introduced in `v0.2.0`_ 47 | 48 | > When calculating the carbon emissions of a pair of sneakers for which we don't have the weight of the upper, we apply a template (which may be based on the shoes' brand and category) that provides this value. The template may be used to determine any value of the data necessary to compute the footprint. 49 | 50 | - Since we may not have exhaustive data for all the products we want to estimate the carbon footprint for, we use a templating system. 51 | - The templating system will _expand_ a partial product data using a specific template. 52 | - The templates' data is defined in [productTemplates.ts](./src/data/productTemplates.ts). 53 | 54 | ## Included data 55 | 56 | The emission factors and model parameters used in our calculation engine are defined in [parameters.ts](./src/data/parameters.ts). 57 | 58 | They are mostly: 59 | 60 | **Emission factors** 61 | 62 | - The most important parameters of the calculation! They determine how much carbon-equivalent emissions a given material or component represents. For example, how much CO2eq a kg of recycled cotton made in Spain emits. 63 | - Emission factors are defined in [parameters.ts](./src/data/parameters.ts), prefixed by `emissionFactor`. 64 | 65 | **Model parameters** 66 | 67 | - For some parts of the carbon footprint assessment, it is less important to have a very detailed analysis (e.g. the most important part - in terms of CO2eq emissions - of the life-cycle of a pair of shoes is the materials it's made of). For those, we may rely on model parameters which provides high-level approximates of the emissions (e.g. the distribution step). 68 | - Those parameters are defined in [parameters.ts](./src/data/parameters.ts), prefixed by `fixedValue`. 69 | 70 | Data-owner? Reach out if you have any remark or question on how we use your data. 71 | 72 | ## Contribute 73 | 74 | If you see errors or want to suggest improvements, feel free to submit Github issues. 75 | 76 | **Data owners** 77 | 78 | - You own data that may help us in our assessment (e.g. emission factors) and would like to share them with us? 79 | - We use some of your data and you have any remark? 80 | 81 | 👉 contact us on [hello@carbonfact.com](mailto:hello@carbonfact.com) 82 | 83 | ## TODOs 84 | 85 | - [x] Explain how to use it 86 | - [ ] Explain how it's used by Carbonfact 87 | - [ ] Detail sources of model parameters 88 | - [ ] Explain how to contribute 89 | - [ ] Add a code of conduct 90 | - [x] Add tests 91 | - [ ] More tests and automated built in CI 92 | 93 | --- 94 | 95 | Copyright © Kanso Inc. 2021 96 | -------------------------------------------------------------------------------- /src/providers/emission-factor-provider.ts: -------------------------------------------------------------------------------- 1 | import { compare } from "../lib/sorting"; 2 | import { modelParameters } from "../data/parameters"; 3 | import { EmissionFactorUnit } from "../entities/EmissionFactorEntity"; 4 | import { ModelParameter } from "../legacy/types"; 5 | import { EmissionFactorEntity } from "../entities/EmissionFactorEntity"; 6 | import { ModelVersion } from "../types"; 7 | import { MissingEmissionFactorMappingError } from "../lib/errors"; 8 | 9 | const emissionFactors = modelParameters.filter((mp) => 10 | mp.id.startsWith("emissionFactor/") 11 | ); 12 | 13 | export interface EmissionFactorProvider { 14 | /** 15 | * Returns the emission factor corresponding to the `id` and 16 | * `country`, whose version is the maximum available one lower 17 | * than `version`. 18 | * 19 | * Throws an error when the emission factor could not be found. 20 | */ 21 | get: ( 22 | id: string, 23 | version: ModelVersion, 24 | countryId: string 25 | ) => EmissionFactorEntity; 26 | 27 | all: () => EmissionFactorEntity[]; 28 | } 29 | 30 | export function buildEmissionFactorProvider(): EmissionFactorProvider { 31 | return { 32 | /** 33 | * Selection process: 34 | * 35 | * - `id` must match with `emissionFactor/`, 36 | * - `version` must be lower than or equal to the one of the emission 37 | * factor, 38 | * - the emission factor's country must match `countryId`. 39 | * 40 | * Throws an error if no result is found. 41 | * 42 | * Expectations 43 | * ------------ 44 | * 45 | * Given the following values in the source data: 46 | * 1. id: emissionFactor/test, country: france, version: 0.1 47 | * 2. id: emissionFactor/test, country: germany, version: 0.2 48 | * 3. id: emissionFactor/test, country: germany, version: 0.3 49 | * 50 | * Expected results: 51 | * 52 | * - id="emissionFactor/test", country="france", version="0.2": 53 | * returns 1 (only one matching the country) 54 | * 55 | * - id="emissionFactor/test", country="portugal", version="0.2", 56 | * throws an error (no match for this country) 57 | * 58 | * - id="emissionFactor/test", country="germany", version="0.3": 59 | * returns 3 (max version number matching the id and country) 60 | * 61 | * - id="emissionFactor/test", country="germany", version="0.1": 62 | * throws an error because no matching emission factor with version 63 | * <= 0.1. 64 | * 65 | * @param id 66 | * @param version 67 | * @param countryId 68 | */ 69 | get: (id: string, version: ModelVersion, countryId: string) => { 70 | // TECHNICAL-DEBT: duplication with ModelParameterProvider 71 | 72 | const inVersionRange = emissionFactors.filter( 73 | (ef) => ef.id === `emissionFactor/${id}` && ef.version <= version 74 | ); 75 | const inVersionRangeAndMatchingCountry = inVersionRange.filter((ef) => 76 | ef.countryIds?.includes(countryId) 77 | ); 78 | inVersionRangeAndMatchingCountry.sort((ef1, ef2) => 79 | compare(ef1.version, ef2.version, "desc") 80 | ); 81 | 82 | let selectedModelParameter: ModelParameter; 83 | if (inVersionRangeAndMatchingCountry.length === 0) 84 | throw new MissingEmissionFactorMappingError(id, countryId, version); 85 | selectedModelParameter = inVersionRangeAndMatchingCountry[0]; 86 | if ( 87 | !selectedModelParameter.countryIds || 88 | selectedModelParameter.countryIds.length === 0 89 | ) { 90 | throw new Error( 91 | `unexpected EmissionFactor "${selectedModelParameter.id}" without countryId` 92 | ); 93 | } 94 | 95 | return { 96 | id, 97 | label: selectedModelParameter.label, 98 | source: selectedModelParameter.source, 99 | value: selectedModelParameter.value, 100 | unit: selectedModelParameter.unit as EmissionFactorUnit, 101 | countryIds: selectedModelParameter.countryIds || [], 102 | version: selectedModelParameter.version, 103 | comments: selectedModelParameter.comments, 104 | }; 105 | }, 106 | 107 | all: () => { 108 | return emissionFactors.map((ef) => { 109 | if (!ef.countryIds || ef.countryIds.length === 0) { 110 | throw new Error( 111 | `unexpected EmissionFactor "${ef.id}" without countryIds` 112 | ); 113 | } 114 | return { 115 | id: ef.id, 116 | label: ef.label, 117 | source: ef.source, 118 | value: ef.value, 119 | unit: ef.unit as EmissionFactorUnit, 120 | countryIds: ef.countryIds, 121 | version: ef.version, 122 | comments: ef.comments, 123 | }; 124 | }); 125 | }, 126 | }; 127 | } 128 | -------------------------------------------------------------------------------- /src/doc/model.md: -------------------------------------------------------------------------------- 1 | # Model versions 2 | 3 | ## 0.5.0 4 | 5 | - Removed the `world` fallback mechanism for emission factors. 6 | - Added and updated emission factors. 7 | - Removed deprecated emission factors and the `deprecated` and `replacedBy` properties. 8 | - Added new product templates, differentiating between leather, plastic and vegetal sneakers. 9 | 10 | _NB: this version is not retro-compatible (it's not possible to compute values for older versions with this implementation). This has been done to enable reducing code and cleaning up the emission factors list._ 11 | 12 | ### Removed the `world` fallback mechanism for emission factors 13 | 14 | In previous versions, the model may use a default value for an emission factor which was not available for the product's country. Some materials had an emission factor associated with the `world`, which would then serve as default value. 15 | 16 | In the beginning, this was useful to simplify managing emission factors. Every new product was manually reviewed, so errors could not be introduced. To import a growing number of products that cannot be all manually reviewed, we need to remove this fallback mechanism that may introduce silent errors. For example, if a new (material, country) combination is introduced, the model may continue to work even if the appropriate emission factor is not present in the parameters, due to the presence of the default value. 17 | 18 | Emission factors are now attached to a list of country IDs. This list makes it explicit to use a given emission factor for a given country. 19 | 20 | ### Added and updated emission factors 21 | 22 | - Emission factors have been added to support values for new countries and new materials. 23 | - Emission factors have been reviewed to ensure they were up-to-date and as accurate as possible. The reviewed emission factors have been updated to model version `0.5.0`. 24 | 25 | ### Removed old and deprecated emission factors, and the `deprecated` and `replacedBy` properties 26 | 27 | The modelling of emission factors and model parameters has been simplified to make it easier to use and less error-prone. 28 | 29 | - Emission factors are attached to a list of countries (instead of "connected entities"), 30 | - `deprecated` and `isReplacedBy` properties have been removed: 31 | 32 | - For auditing purposes, the versioning of the model parameters via Git could be used instead of keeping old and deprecated emission factors. 33 | - While this could be used to make more dynamic changes in emission factors, until it was implemented in the model, this could only be a source of errors (e.g. by using a deprecated emission factor). 34 | 35 | History of emission factors (previous model versions) have been removed too. 36 | 37 | ## 0.4.0 38 | 39 | ### Multiple distribution mode 40 | 41 | We now support the following emission values for distribution mode: 42 | 43 | - [intercontinental (default)](https://github.com/kansoapp/carbonfact-models/blob/1a0c644d76ba3f91e11af3381d34c5d12484c44a/src/data/parameters.ts#L1532) 44 | - [sea only](https://github.com/kansoapp/carbonfact-models/blob/1a0c644d76ba3f91e11af3381d34c5d12484c44a/src/data/parameters.ts#L1543) 45 | - [sea only biofuel](https://github.com/kansoapp/carbonfact-models/blob/1a0c644d76ba3f91e11af3381d34c5d12484c44a/src/data/parameters.ts#L1554) 46 | - [intracontinental](https://github.com/kansoapp/carbonfact-models/blob/1a0c644d76ba3f91e11af3381d34c5d12484c44a/src/data/parameters.ts#L1565) 47 | 48 | ### Materials 49 | 50 | In the 0.3.0 release we introduced the following capabilities: 51 | 52 | - Introduced possibility to have a component made of a mix of materials. 53 | - Introduced component country of origin, to associate the emission factors for the materials of this component with this country rather than with the manufacturing country. 54 | 55 | ### End of life 56 | 57 | We support custom parameter for end of life emission factor when that information is provided in Lifecycle Analysis. 58 | 59 | ## 0.3.0 60 | 61 | - Introduced possibility to have a component made of a mix of materials. 62 | - Introduced component country of origin, to associate the emission factors for the materials of this component with this country rather than with the manufacturing country. 63 | 64 | ## 0.2.0 65 | 66 | - Completed the model with additional emission factors, improved some emission factors, in particular adding more details on the sources. 67 | - Introduced a solution to expand `PartialProductData` and fill some characteristics from a template. For example: 68 | - Assume we have sneaker from Brand A. 69 | - Brand A shared with us the average weight distribution of its sneakers and the outsole is 55%. 70 | - If we add a new product from Brand A but we don't know the weight of the outsole, specifying a template would enable us to use the average shared by the brand and use it for this product. 71 | - If we get the real measure for this product later, we can still fill the field, the template will then not be used (for this field at list). 72 | - The `ProductData` schema was updated to `v0.2.0` to support partial data. 73 | 74 | ## 0.1.0 75 | 76 | - Initial model version, fitted to support calculating the footprint the first products we submitted. 77 | - The model was using `ProductData` in `v0.1.0` which was providing values for all fields in the schema. 78 | -------------------------------------------------------------------------------- /src/data/productDataTemplates.ts: -------------------------------------------------------------------------------- 1 | import { ProductDataTemplateEntity } from "../entities/ProductDataTemplateEntity"; 2 | import { ModelVersion } from "../types"; 3 | 4 | export const productDataTemplates: ProductDataTemplateEntity[] = [ 5 | { 6 | id: "shoes/sneakers/generic", 7 | label: "Default (internal)", 8 | source: "Internal Carbonfact template", 9 | modelVersion: ModelVersion.version_0_1_0, 10 | productCategorySlug: "sneakers", 11 | weight: 0.7, 12 | components: [ 13 | { 14 | componentId: "upper", 15 | materials: [ 16 | { 17 | materialId: "cotton/standard", 18 | proportion: 1.0, 19 | }, 20 | ], 21 | materialCountryId: "china", 22 | proportion: 0.3, 23 | }, 24 | { 25 | componentId: "outsole", 26 | materials: [ 27 | { 28 | materialId: "rubber/undetermined", 29 | proportion: 1.0, 30 | }, 31 | ], 32 | materialCountryId: "china", 33 | proportion: 0.55, 34 | }, 35 | ], 36 | manufacturingCountryId: "china", 37 | endOfLifeRecyclingProgram: false, // TECHNICAL-DEBT to be removed after migration of existing data 38 | endOfLifeOption: "withoutRecyclingProgram", 39 | endOfLifeValue: null, 40 | }, 41 | { 42 | id: "shoes/sneakers/sneakers-leather", 43 | label: "Sneakers Leather - Reference model", 44 | source: "Internal Carbonfact template", 45 | modelVersion: ModelVersion.version_0_4_2, 46 | productCategorySlug: "sneakers-leather", 47 | weight: 0.7, 48 | components: [ 49 | { 50 | componentId: "upper", 51 | materials: [ 52 | { 53 | materialId: "leather/cattle", 54 | proportion: 1.0, 55 | }, 56 | ], 57 | materialCountryId: "china", 58 | proportion: 0.3, 59 | }, 60 | { 61 | componentId: "outsole", 62 | materials: [ 63 | { 64 | materialId: "eva/standard", 65 | proportion: 1.0, 66 | }, 67 | ], 68 | materialCountryId: "china", 69 | proportion: 0.55, 70 | }, 71 | ], 72 | manufacturingCountryId: "china", 73 | endOfLifeRecyclingProgram: false, // TECHNICAL-DEBT to be removed after migration of existing data 74 | endOfLifeOption: "withoutRecyclingProgram", 75 | endOfLifeValue: null, 76 | }, 77 | { 78 | id: "shoes/sneakers/sneakers-plastic", 79 | label: "Sneakers Plastic - Reference model", 80 | source: "Internal Carbonfact template", 81 | modelVersion: ModelVersion.version_0_4_2, 82 | productCategorySlug: "sneakers-plastic", 83 | weight: 0.7, 84 | components: [ 85 | { 86 | componentId: "upper", 87 | materials: [ 88 | { 89 | materialId: "polyester/standard", 90 | proportion: 1.0, 91 | }, 92 | ], 93 | materialCountryId: "china", 94 | proportion: 0.3, 95 | }, 96 | { 97 | componentId: "outsole", 98 | materials: [ 99 | { 100 | materialId: "eva/standard", 101 | proportion: 1.0, 102 | }, 103 | ], 104 | materialCountryId: "china", 105 | proportion: 0.55, 106 | }, 107 | ], 108 | manufacturingCountryId: "china", 109 | endOfLifeRecyclingProgram: false, // TECHNICAL-DEBT to be removed after migration of existing data 110 | endOfLifeOption: "withoutRecyclingProgram", 111 | endOfLifeValue: null, 112 | }, 113 | { 114 | id: "shoes/sneakers/sneakers-natural-fiber", 115 | label: "Sneakers Natural Fiber - Reference model", 116 | source: "Internal Carbonfact template", 117 | modelVersion: ModelVersion.version_0_4_2, 118 | productCategorySlug: "sneakers-natural-fiber", 119 | weight: 0.7, 120 | components: [ 121 | { 122 | componentId: "upper", 123 | materials: [ 124 | { 125 | materialId: "cotton/standard", 126 | proportion: 1.0, 127 | }, 128 | ], 129 | materialCountryId: "china", 130 | proportion: 0.3, 131 | }, 132 | { 133 | componentId: "outsole", 134 | materials: [ 135 | { 136 | materialId: "eva/standard", 137 | proportion: 1.0, 138 | }, 139 | ], 140 | materialCountryId: "china", 141 | proportion: 0.55, 142 | }, 143 | ], 144 | manufacturingCountryId: "china", 145 | endOfLifeRecyclingProgram: false, // TECHNICAL-DEBT to be removed after migration of existing data 146 | endOfLifeOption: "withoutRecyclingProgram", 147 | endOfLifeValue: null, 148 | }, 149 | // TECHNICAL-DEBT: should be removed (requires to migrate submissions using this template) 150 | { 151 | id: "shoes/sneakers/caval_samieco_lca_2021", 152 | label: "Caval (LCA 2021)", 153 | source: "Information from Caval's LCA 2021 report produced by Sami.eco", 154 | modelVersion: ModelVersion.version_0_2_0, 155 | productCategorySlug: "sneakers", 156 | weight: 0.7, 157 | components: [ 158 | { 159 | componentId: "upper", 160 | materials: [ 161 | { 162 | materialId: "cotton/standard", 163 | proportion: 1.0, 164 | }, 165 | ], 166 | materialCountryId: "china", 167 | proportion: 0.3, 168 | }, 169 | { 170 | componentId: "outsole", 171 | materials: [ 172 | { 173 | materialId: "rubber/undetermined", 174 | proportion: 1.0, 175 | }, 176 | ], 177 | materialCountryId: "china", 178 | proportion: 0.55, 179 | }, 180 | // TODO: complete with other values from the report 181 | ], 182 | manufacturingCountryId: "china", 183 | endOfLifeRecyclingProgram: false, 184 | endOfLifeOption: "withoutRecyclingProgram", 185 | endOfLifeValue: null, 186 | }, 187 | ]; 188 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/operations/compute-product-footprint.ts: -------------------------------------------------------------------------------- 1 | import { EmissionFactorProvider } from "../providers/emission-factor-provider"; 2 | import { ModelParameterEntity } from "../entities/ModelParameterEntity"; 3 | import { ProductDataEntity } from "../entities/ProductDataEntity"; 4 | import { 5 | ProductFootprintEntity, 6 | ProductFootprintExplanation, 7 | } from "../entities/ProductFootprintEntity"; 8 | import { ModelVersion } from "../types"; 9 | import { checkDataValidity } from "./expand-partial-product-data"; 10 | import { ModelParameterProvider } from "../providers/model-parameter-provider"; 11 | import { EmissionFactorEntity } from "../entities/EmissionFactorEntity"; 12 | 13 | type ComputeProductFootprintOperation = ( 14 | emissionFactorProvider: EmissionFactorProvider, 15 | modelParameterProvider: ModelParameterProvider 16 | ) => ( 17 | productDataEntity: ProductDataEntity, 18 | modelVersion: ModelVersion 19 | ) => ProductFootprintEntity; 20 | 21 | /** 22 | * Computes the carbon footprint using Carbonfact's simplified LCA model 23 | * for the specified `ProductDataEntity`. 24 | * 25 | * Scope 26 | * ----- 27 | * This use case processes complete data and does not perform data extension 28 | * using a template. This is out-of-scope for this use case as it would violate 29 | * the Single-Responsibility-Principle. Another use case should handle the 30 | * expansion. The resulting complete product data could then be passed to this 31 | * use case. 32 | * 33 | * Dependencies 34 | * ------------ 35 | * This use case depends on `EmissionFactorProvider` and `ModelParameterProvider`. 36 | * They provide the emission factors and model parameters required to perform 37 | * the footprint calculation. 38 | * 39 | * @param emissionFactorProvider 40 | * @param modelParameterProvider 41 | * @returns 42 | */ 43 | export const ComputeProductFootprintOperation: ComputeProductFootprintOperation = 44 | ( 45 | emissionFactorProvider: EmissionFactorProvider, 46 | modelParameterProvider: ModelParameterProvider 47 | ) => { 48 | return (data: ProductDataEntity, modelVersion: ModelVersion) => { 49 | checkDataValidity(data); 50 | const { weight, manufacturingCountryId } = data; 51 | 52 | // Materials 53 | // --------- 54 | const explanationMaterialsComponents: ProductFootprintExplanation["materials"]["components"] = 55 | []; 56 | let explanationMaterialsHumanReadable = ""; 57 | let materialsTotal = 0; 58 | for (const component of data.components) { 59 | // Adding the emissions for each component of the product. 60 | 61 | let componentWeight = component.proportion * weight; 62 | let componentEmissions = 0.0; 63 | 64 | const explanationMaterialItems: any[] = []; 65 | for (const materialItem of component.materials) { 66 | // Adding the emissions for each material of the component. 67 | // TECHNICAL-DEBT check sum of components.materials.proportion <= 1.0 68 | 69 | let emissionFactor: EmissionFactorEntity; 70 | if (materialItem.materialId === "missingMaterialPart") { 71 | // TECHNICAL-DEBT relying on "missingMaterialPart" should 72 | // be removed. This is a special case where we have only 73 | // one emission factor, not attached to a specific country. 74 | // This should be done differently in our carbon-model. 75 | emissionFactor = emissionFactorProvider.get( 76 | "material/missingMaterialPart", 77 | modelVersion, 78 | "world" 79 | ); 80 | } else { 81 | emissionFactor = emissionFactorProvider.get( 82 | "material/" + materialItem.materialId, 83 | modelVersion, 84 | component.materialCountryId 85 | ); 86 | } 87 | 88 | const materialItemEmissions = 89 | emissionFactor.value * componentWeight * materialItem.proportion; 90 | componentEmissions += materialItemEmissions; 91 | 92 | explanationMaterialItems.push({ 93 | materialId: materialItem.materialId, 94 | proportion: materialItem.proportion, 95 | emissionFactor: emissionFactor, 96 | humanReadable: `${materialItem.proportion * 100}% ${ 97 | materialItem.materialId 98 | } ${emissionFactor.value} ${emissionFactor.unit} * ${ 99 | component.componentId 100 | } proportion ${ 101 | component.proportion * 100 102 | }% * product weight ${weight} kg = ${materialItemEmissions} kgCO2e`, 103 | }); 104 | } 105 | explanationMaterialsComponents.push({ 106 | componentId: component.componentId, 107 | materials: explanationMaterialItems, 108 | proportion: component.proportion, 109 | total: componentEmissions, 110 | unit: "kgCO2eq", 111 | }); 112 | 113 | materialsTotal += componentEmissions; 114 | } 115 | explanationMaterialsHumanReadable = `total = ${materialsTotal} kgCO2e`; 116 | 117 | // Manufacturing 118 | let manufacturingTotal = 0; 119 | const manufacturingEnergy = modelParameterProvider.get( 120 | "lifeCycleAnalysisStep/manufacturing/shoes/energyConsumption/electricity", 121 | modelVersion 122 | ); 123 | const electricityEmissionFactor = emissionFactorProvider.get( 124 | "energy/electricity", 125 | modelVersion, 126 | manufacturingCountryId 127 | ); 128 | manufacturingTotal = 129 | manufacturingEnergy.value * electricityEmissionFactor.value; 130 | // TECHNICAL-DEBT: not handling/checking units 131 | 132 | const explanationManufacturingHumanReadable = `energy (${ 133 | manufacturingEnergy.value 134 | } kWh) * electricity intensity for ${electricityEmissionFactor.countryIds.join( 135 | ", " 136 | )} (${ 137 | electricityEmissionFactor.value 138 | } kgCO2e/kWh) = ${manufacturingTotal} kgCO2e`; 139 | 140 | // Distribution 141 | let distributionTotal = 0; 142 | const distributionModelParameter = selectDistributionModelParameter( 143 | modelParameterProvider, 144 | data.distributionMode, 145 | modelVersion 146 | ); 147 | distributionTotal = distributionModelParameter.value; 148 | // TECHNICAL-DEBT: no handling/checking units 149 | 150 | const explanationDistributionHumanReadable = `${distributionModelParameter.description}: ${distributionModelParameter.value} ${distributionModelParameter.unit}`; 151 | 152 | // Use 153 | let useTotal = 0; 154 | useTotal = modelParameterProvider.get( 155 | "lifeCycleAnalysisStep/use/shoes", 156 | modelVersion 157 | ).value; 158 | // TECHNICAL-DEBT: not handling case where it's missing 159 | // TECHNICAL-DEBT: not handling/checking unit 160 | 161 | const explanationUseHumanReadable = `"use" step: fixed value = ${useTotal} kgCO2e`; 162 | 163 | // End of life 164 | let endOfLifeTotal = 0; 165 | const endOfLifeOption = data.endOfLifeOption; 166 | let endOfLifeModelParameter: ModelParameterEntity | undefined = undefined; 167 | let explanationEndOfLifeHumanReadable: string = ""; 168 | 169 | if (endOfLifeOption !== "custom") { 170 | const endOfLifeParameterId = `lifeCycleAnalysisStep/endOfLife/shoes/${endOfLifeOption}`; 171 | endOfLifeModelParameter = modelParameterProvider.get( 172 | endOfLifeParameterId, 173 | modelVersion 174 | ); 175 | endOfLifeTotal = endOfLifeModelParameter.value; 176 | explanationEndOfLifeHumanReadable = `${endOfLifeModelParameter.description}: ${endOfLifeModelParameter.value} ${endOfLifeModelParameter.unit}`; 177 | } else { 178 | if (!data.endOfLifeValue) { 179 | throw new Error( 180 | `unexpected: null "endOfLifeValue" with option="custom"` 181 | ); 182 | } 183 | endOfLifeTotal = data.endOfLifeValue; 184 | explanationEndOfLifeHumanReadable = `Value estimated by the brand according to its own recycling programs for this product: ${data.endOfLifeValue} kgCO2eq`; 185 | } 186 | 187 | const footprintTotal = 188 | materialsTotal + 189 | manufacturingTotal + 190 | distributionTotal + 191 | useTotal + 192 | endOfLifeTotal; 193 | const explanationTotalHumanReadable = `${footprintTotal} kgCO2e`; 194 | const explanation: ProductFootprintExplanation = { 195 | materials: { 196 | components: explanationMaterialsComponents, 197 | humanReadable: explanationMaterialsHumanReadable, 198 | }, 199 | manufacturing: { 200 | humanReadable: explanationManufacturingHumanReadable, 201 | }, 202 | distribution: { 203 | modelParameter: distributionModelParameter, 204 | humanReadable: explanationDistributionHumanReadable, 205 | }, 206 | use: { 207 | humanReadable: explanationUseHumanReadable, 208 | }, 209 | endOfLife: { 210 | modelParameter: endOfLifeModelParameter, 211 | humanReadable: explanationEndOfLifeHumanReadable, 212 | }, 213 | total: { 214 | humanReadable: explanationTotalHumanReadable, 215 | }, 216 | }; 217 | return { 218 | total: footprintTotal, 219 | breakdown: { 220 | materials: materialsTotal, 221 | manufacturing: manufacturingTotal, 222 | distribution: distributionTotal, 223 | use: useTotal, 224 | endOfLife: endOfLifeTotal, 225 | }, 226 | explanation, 227 | }; 228 | }; 229 | }; 230 | 231 | /** 232 | * For retro-compatibility, to support model version before 0.4.0 which 233 | * changed the way the distribution emissions fixed value was selected. 234 | */ 235 | function selectDistributionModelParameter( 236 | modelParameterProvider: ModelParameterProvider, 237 | distributionMode: string, 238 | modelVersion: ModelVersion 239 | ) { 240 | return modelParameterProvider.get( 241 | `lifeCycleAnalysisStep/distribution/shoes/${distributionMode}`, 242 | modelVersion 243 | ); 244 | } 245 | -------------------------------------------------------------------------------- /src/operations/compute-product-footprint.test.ts: -------------------------------------------------------------------------------- 1 | import { Unit as ModelParameterUnit } from "../entities/ModelParameterEntity"; 2 | import { 3 | EmissionFactorEntity, 4 | EmissionFactorUnit, 5 | } from "../entities/EmissionFactorEntity"; 6 | import { ProductDataEntity } from "../entities/ProductDataEntity"; 7 | import { ModelVersion } from "../types"; 8 | import { ComputeProductFootprintOperation } from "./compute-product-footprint"; 9 | import { EmissionFactorProvider } from "../providers/emission-factor-provider"; 10 | import { ModelParameterProvider } from "../providers/model-parameter-provider"; 11 | 12 | const MANUFACTURING_ELECTRICITY = 10; 13 | 14 | const testData1: ProductDataEntity = { 15 | weight: 0.6, 16 | components: [ 17 | { 18 | componentId: "upper", 19 | materials: [ 20 | { 21 | materialId: "material1", 22 | proportion: 1.0, 23 | }, 24 | ], 25 | materialCountryId: "china", 26 | proportion: 0.3, 27 | }, 28 | { 29 | componentId: "outsole", 30 | materials: [ 31 | { 32 | materialId: "material2", 33 | proportion: 1.0, 34 | }, 35 | ], 36 | materialCountryId: "china", 37 | proportion: 0.55, 38 | }, 39 | { 40 | componentId: "other", 41 | materials: [ 42 | { 43 | materialId: "missingMaterialPart", 44 | proportion: 1.0, 45 | }, 46 | ], 47 | materialCountryId: "china", 48 | proportion: 0.15, 49 | }, 50 | ], 51 | manufacturingCountryId: "china", 52 | distributionMode: "some-mode", 53 | endOfLifeOption: "endOfLifeOption1", 54 | endOfLifeValue: null, 55 | }; 56 | 57 | const testData2: ProductDataEntity = { 58 | weight: 0.7, 59 | components: [ 60 | { 61 | componentId: "upper", 62 | materials: [ 63 | { 64 | materialId: "material1", 65 | proportion: 0.7, 66 | }, 67 | { 68 | materialId: "material2", 69 | proportion: 0.3, 70 | }, 71 | ], 72 | materialCountryId: "china", 73 | proportion: 0.5, 74 | }, 75 | { 76 | componentId: "outsole", 77 | materials: [ 78 | { 79 | materialId: "material1", 80 | proportion: 1.0, 81 | }, 82 | ], 83 | materialCountryId: "china", 84 | proportion: 0.5, 85 | }, 86 | ], 87 | manufacturingCountryId: "china", 88 | distributionMode: "some-mode", 89 | endOfLifeOption: "endOfLifeOption2", 90 | endOfLifeValue: null, 91 | }; 92 | 93 | const testData3: ProductDataEntity = { 94 | weight: 0.7, 95 | components: [ 96 | { 97 | componentId: "upper", 98 | materials: [ 99 | { 100 | materialId: "material1", 101 | proportion: 0.7, 102 | }, 103 | { 104 | materialId: "material2", 105 | proportion: 0.3, 106 | }, 107 | ], 108 | materialCountryId: "china", 109 | proportion: 0.5, 110 | }, 111 | { 112 | componentId: "outsole", 113 | materials: [ 114 | { 115 | materialId: "material1", 116 | proportion: 1.0, 117 | }, 118 | ], 119 | materialCountryId: "portugal", 120 | proportion: 0.5, 121 | }, 122 | ], 123 | manufacturingCountryId: "portugal", 124 | distributionMode: "some-mode", 125 | endOfLifeOption: "custom", 126 | endOfLifeValue: 0.03, 127 | }; 128 | 129 | const emissionFactorProvider: EmissionFactorProvider = { 130 | get: (id, version, countryId) => { 131 | function params(id: string): { value: number; unit: EmissionFactorUnit } { 132 | switch (id) { 133 | case "material/material1": 134 | switch (countryId) { 135 | case "china": 136 | return { value: 10.0, unit: "kgCO2eq/kg" }; 137 | case "portugal": 138 | return { value: 5.0, unit: "kgCO2eq/kg" }; 139 | } 140 | case "material/material2": 141 | switch (countryId) { 142 | case "china": 143 | return { value: 20.0, unit: "kgCO2eq/kg" }; 144 | } 145 | case "material/missingMaterialPart": 146 | return { value: 30.0, unit: "kgCO2eq/kg" }; 147 | case "energy/electricity": 148 | switch (countryId) { 149 | case "china": 150 | return { value: 0.5, unit: "kgCO2eq/kWh" }; 151 | case "portugal": 152 | return { value: 0.1, unit: "kgCO2eq/kWh" }; 153 | } 154 | default: 155 | throw new Error( 156 | `no emission factor for "${id}", ${countryId}, ${version}` 157 | ); 158 | } 159 | } 160 | const { value, unit } = params(id); 161 | return { 162 | id: id, 163 | label: `Label for ${id}`, 164 | source: `Source for ${id}`, 165 | value: value, 166 | unit: unit, 167 | countryIds: [countryId], 168 | version: version, 169 | }; 170 | }, 171 | all: () => [] as EmissionFactorEntity[], 172 | }; 173 | 174 | const modelParameterProvider: ModelParameterProvider = { 175 | get: (id: string, version: ModelVersion, countryId?: string) => { 176 | function params(id: string): { value: number; unit: ModelParameterUnit } { 177 | switch (id) { 178 | case "lifeCycleAnalysisStep/manufacturing/shoes/energyConsumption/electricity": 179 | return { value: MANUFACTURING_ELECTRICITY, unit: "kWh" }; 180 | case "lifeCycleAnalysisStep/distribution/shoes/some-mode": 181 | if (version < ModelVersion.version_0_4_0) { 182 | throw new Error( 183 | "no .../distribution/shoes/... model parameter before v0.4.0" 184 | ); 185 | } 186 | return { value: 2, unit: "kgCO2eq" }; 187 | case "lifeCycleAnalysisStep/distribution/shoes": 188 | return { value: 2.2, unit: "kgCO2eq" }; 189 | case "lifeCycleAnalysisStep/use/shoes": 190 | return { value: 1, unit: "kgCO2eq" }; 191 | case "lifeCycleAnalysisStep/endOfLife/shoes/endOfLifeOption1": 192 | return { value: 0.01, unit: "kgCO2eq" }; 193 | case "lifeCycleAnalysisStep/endOfLife/shoes/endOfLifeOption2": 194 | return { value: 0.02, unit: "kgCO2eq" }; 195 | default: 196 | throw new Error( 197 | `No model parameter for id "${id}" for version <= ${version}` 198 | ); 199 | } 200 | } 201 | const { value, unit } = params(id); 202 | return { 203 | id: id, 204 | description: "Model parameter ${id}", 205 | source: `Source for model parameter ${id}`, 206 | value: value, 207 | unit: unit, 208 | countryId: countryId, 209 | version: version, 210 | }; 211 | }, 212 | }; 213 | 214 | const computeProductFootprint = ComputeProductFootprintOperation( 215 | emissionFactorProvider, 216 | modelParameterProvider 217 | ); 218 | 219 | test("ComputeProductFootprint(testData1) is correct", () => { 220 | const data = testData1; 221 | const resultFootprint = computeProductFootprint(data, ModelVersion.current); 222 | expect(resultFootprint.breakdown.materials).toBeCloseTo( 223 | 0.6 * (0.3 * 10 + 0.55 * 20 + 0.15 * 30) 224 | ); 225 | expect(resultFootprint.breakdown.manufacturing).toBeCloseTo( 226 | MANUFACTURING_ELECTRICITY * 0.5 227 | ); 228 | expect(resultFootprint.breakdown.distribution).toBeCloseTo(2); 229 | expect(resultFootprint.breakdown.use).toBeCloseTo(1); 230 | expect(resultFootprint.breakdown.endOfLife).toBeCloseTo(0.01); 231 | }); 232 | 233 | test("ComputeProductFootprint(testData2) is correct", () => { 234 | const data = testData2; 235 | const resultFootprint = computeProductFootprint(data, ModelVersion.current); 236 | expect(resultFootprint.breakdown.materials).toBeCloseTo( 237 | 0.7 * 0.5 * (10 * 0.7 + 20 * 0.3) + 0.7 * 0.5 * 10 238 | ); 239 | expect(resultFootprint.breakdown.manufacturing).toBeCloseTo( 240 | MANUFACTURING_ELECTRICITY * 0.5 241 | ); 242 | expect(resultFootprint.breakdown.distribution).toBeCloseTo(2); 243 | expect(resultFootprint.breakdown.use).toBeCloseTo(1); 244 | expect(resultFootprint.breakdown.endOfLife).toBeCloseTo(0.02); 245 | }); 246 | 247 | test("ComputeProductFootprint(testData3) is correct", () => { 248 | const data = testData3; 249 | const resultFootprint = computeProductFootprint(data, ModelVersion.current); 250 | expect(resultFootprint.breakdown.materials).toBeCloseTo( 251 | 0.7 * (0.5 * (0.7 * 10 + 0.3 * 20) + 0.5 * (1.0 * 5)) 252 | ); 253 | expect(resultFootprint.breakdown.manufacturing).toBeCloseTo( 254 | MANUFACTURING_ELECTRICITY * 0.1 255 | ); 256 | expect(resultFootprint.breakdown.distribution).toBeCloseTo(2); 257 | expect(resultFootprint.breakdown.use).toBeCloseTo(1); 258 | expect(resultFootprint.breakdown.endOfLife).toBeCloseTo(0.03); 259 | }); 260 | 261 | test("ComputeProductFootprint(...) raises an error when a material emission factor is missing", () => { 262 | const testData: ProductDataEntity = { 263 | weight: 0.6, 264 | components: [ 265 | { 266 | componentId: "upper", 267 | materials: [ 268 | { 269 | materialId: "missingMaterial", 270 | proportion: 1.0, 271 | }, 272 | ], 273 | materialCountryId: "china", 274 | proportion: 1.0, 275 | }, 276 | ], 277 | manufacturingCountryId: "china", 278 | distributionMode: "intercontinental/default", 279 | endOfLifeOption: "anyway", 280 | endOfLifeValue: null, 281 | }; 282 | expect(() => computeProductFootprint(testData, ModelVersion.current)).toThrow( 283 | 'no emission factor for "material/missingMaterial"' 284 | ); 285 | }); 286 | 287 | test("ComputeProductFootprint(invalid) raises an error if the sum of components.proportion is not 1.0", () => { 288 | const testData: ProductDataEntity = { 289 | weight: 0.6, 290 | components: [ 291 | { 292 | componentId: "upper", 293 | materials: [ 294 | { 295 | materialId: "missingMaterial", 296 | proportion: 1.0, 297 | }, 298 | ], 299 | materialCountryId: "china", 300 | proportion: 0.3, 301 | }, 302 | ], 303 | manufacturingCountryId: "china", 304 | distributionMode: "intercontinental/default", 305 | endOfLifeOption: "endOfLifeOption1", 306 | endOfLifeValue: null, 307 | }; 308 | expect(() => computeProductFootprint(testData, ModelVersion.current)).toThrow( 309 | "invalid data, sum of proportions of components < 1.0 (0.3)" 310 | ); 311 | }); 312 | -------------------------------------------------------------------------------- /src/operations/expand-partial-product-data.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PartialProductDataEntityComponent, 3 | PartialProductDataEntity, 4 | } from "../entities/PartialProductDataEntity"; 5 | import { 6 | ProductDataEntity, 7 | ProductDataEntityComponent, 8 | } from "../entities/ProductDataEntity"; 9 | import { 10 | buildGeographicalAreaProvider, 11 | IGeographicalAreaProvider, 12 | } from "../providers/geographical-area"; 13 | import { IProductDataTemplateProvider } from "../providers/product-data-template-provider"; 14 | import { ModelVersion } from "../types"; 15 | 16 | const FLOAT_ERROR_MARGIN = 0.001; 17 | // TECHNICAL-DEBT should look at what's a good value 18 | 19 | type ExpandPartialProductDataEntityOperation = ( 20 | productDataTemplateProvider: IProductDataTemplateProvider, 21 | geographicalAreaProvider: IGeographicalAreaProvider 22 | ) => ( 23 | partialProductData: PartialProductDataEntity, 24 | templateId: string, 25 | modelVersion: ModelVersion 26 | ) => ProductDataEntity; 27 | 28 | /** 29 | * Expands a partial product data (`PartialProductDataEntity`) 30 | * into a complete product data (`ProductDataEntity`). 31 | * 32 | * The expansion is done by applying an algorithm specified in 33 | * [doc/expansion.md](../doc/expansion.md). 34 | * 35 | * ### Dependencies 36 | * 37 | * - `ProductDataTemplateProvider`: provides the template for the 38 | * expansion, based on its `id`. 39 | * 40 | * @param productDataTemplateProvider 41 | */ 42 | export const ExpandPartialProductDataEntityOperation: ExpandPartialProductDataEntityOperation = 43 | ( 44 | productDataTemplateProvider: IProductDataTemplateProvider, 45 | geographicalAreaProvider = buildGeographicalAreaProvider() 46 | ) => { 47 | return (src, templateId, modelVersion) => { 48 | const template = productDataTemplateProvider.get( 49 | templateId, 50 | modelVersion 51 | ); 52 | const unidentifiedMaterialId = "missingMaterialPart"; // TECHNICAL-DEBT: only for "shoes/sneakers" 53 | 54 | let expanded: PartialProductDataEntity = { 55 | weight: src.weight, 56 | components: src.components?.map((c) => ({ 57 | componentId: c.componentId, 58 | materials: c.materials?.map((m) => ({ 59 | materialId: m.materialId, 60 | proportion: m.proportion, 61 | })), 62 | materialCountryId: c.materialCountryId, 63 | proportion: c.proportion, 64 | })), 65 | manufacturingCountryId: src.manufacturingCountryId, 66 | endOfLifeRecyclingProgram: src.endOfLifeRecyclingProgram, 67 | }; // deep copy 68 | 69 | // 1. Expand missing material country id with 70 | // manufacturing country. 71 | if (expanded.components) { 72 | for (const c of expanded.components) { 73 | c.materialCountryId ||= 74 | src.manufacturingCountryId || template.manufacturingCountryId; 75 | } 76 | } 77 | 78 | // 2. Expand from template 79 | 80 | // End-of-life fields 81 | // DEPRECATED: handling endOfLifeRecyclingProgram is for retro-compatibility 82 | // with older product data schemas before ...Option and ...Value were 83 | // introduced. 84 | // TECHNICAL-DEBT: remove this after having migrated the data 85 | let expandedEndOfLifeOption: string; 86 | let expandedEndOfLifeValue: number | null; 87 | if (src.endOfLifeRecyclingProgram !== undefined) { 88 | if (src.endOfLifeOption) 89 | throw new Error( 90 | "invalid partial with both legacy and new values for `endOfLife`" 91 | ); 92 | expandedEndOfLifeOption = src.endOfLifeRecyclingProgram 93 | ? "withRecyclingProgram" 94 | : "withoutRecyclingProgram"; 95 | expandedEndOfLifeValue = null; 96 | } else { 97 | expandedEndOfLifeOption = 98 | src.endOfLifeOption || template.endOfLifeOption; 99 | expandedEndOfLifeValue = 100 | expandedEndOfLifeOption === "custom" 101 | ? src.endOfLifeValue || template.endOfLifeValue 102 | : null; 103 | if (expandedEndOfLifeOption === "custom" && !expandedEndOfLifeValue) { 104 | throw new Error(`unexpected null endOfLifeValue with option=custom`); 105 | } 106 | } 107 | 108 | expanded = { 109 | weight: src.weight || template.weight, 110 | components: expandComponentsFromTemplate(expanded, template.components), 111 | manufacturingCountryId: 112 | src.manufacturingCountryId || template.manufacturingCountryId, 113 | endOfLifeRecyclingProgram: 114 | src.endOfLifeRecyclingProgram === undefined 115 | ? template.endOfLifeRecyclingProgram 116 | : src.endOfLifeRecyclingProgram, 117 | endOfLifeOption: expandedEndOfLifeOption, 118 | endOfLifeValue: expandedEndOfLifeValue, 119 | }; 120 | 121 | if (!expanded.components) 122 | throw new Error( 123 | "unexpected empty results.components, should have been filled with template's" 124 | ); 125 | 126 | // 3. Expanding missing material proportions 127 | for (const c of expanded.components) { 128 | if (c.materials && c.materials.length > 0) { 129 | const materialsProportionSum = sumProportions(c.materials); 130 | if (materialsProportionSum < 1.0) { 131 | const materialsWithUndefinedProportion = c.materials.filter( 132 | (m) => !m.proportion 133 | ); 134 | if (materialsWithUndefinedProportion.length > 0) { 135 | // Filling equally all materials with undefined proportions 136 | // with the remaining proportion. 137 | for (const m of materialsWithUndefinedProportion) { 138 | m.proportion = 139 | (1.0 - materialsProportionSum) / 140 | materialsWithUndefinedProportion.length; 141 | } 142 | } else { 143 | // Adding a default material for the remaining proportion. 144 | // TECHNICAL-DEBT: this default material should be selected 145 | // according to the component. 146 | c.materials.push({ 147 | materialId: unidentifiedMaterialId, 148 | proportion: 1.0 - materialsProportionSum, 149 | }); 150 | } 151 | } 152 | } else 153 | throw new Error( 154 | `unexpected error, components[${c.componentId}].materials should have been completed from template` 155 | ); 156 | } 157 | 158 | // 4. Expanding components with an "other" component for the remaining proportion 159 | const componentsProportionSum = sumProportions(expanded.components); 160 | if (componentsProportionSum < 1.0) { 161 | expanded.components.push({ 162 | componentId: "other", 163 | materials: [ 164 | { 165 | materialId: unidentifiedMaterialId, 166 | proportion: 1.0, 167 | }, 168 | ], 169 | materialCountryId: 170 | src.manufacturingCountryId || template.manufacturingCountryId, 171 | proportion: 1.0 - componentsProportionSum, 172 | }); 173 | } 174 | 175 | // 5. Expanding `distributionMode` 176 | expanded.distributionMode = 177 | src.distributionMode || 178 | distributionModeForCountry( 179 | geographicalAreaProvider, 180 | expanded.manufacturingCountryId! 181 | ); 182 | 183 | // TECHNICAL-DEBT should check expanded is a valid `ProductDataEntity` 184 | return expanded as ProductDataEntity; 185 | }; 186 | }; 187 | 188 | function expandComponentsFromTemplate( 189 | partial: PartialProductDataEntity, 190 | templateComponents: ProductDataEntityComponent[] 191 | ): PartialProductDataEntityComponent[] { 192 | const srcComponents: PartialProductDataEntityComponent[] | undefined = 193 | partial.components; 194 | 195 | if (!srcComponents || srcComponents.length === 0) { 196 | return templateComponents.map((c) => ({ 197 | componentId: c.componentId, 198 | materials: c.materials.map((m) => ({ 199 | materialId: m.materialId, 200 | proportion: m.proportion, 201 | })), 202 | materialCountryId: c.materialCountryId, 203 | proportion: c.proportion, 204 | })); 205 | } 206 | 207 | const resultComponents: PartialProductDataEntityComponent[] = []; 208 | for (let tc of templateComponents) { 209 | const sc = srcComponents.find((c) => c.componentId === tc.componentId); 210 | const tcCopy = { 211 | componentId: tc.componentId, 212 | materials: tc.materials.map((m) => ({ 213 | materialId: m.materialId, 214 | proportion: m.proportion, 215 | })), 216 | materialCountryId: tc.materialCountryId, 217 | proportion: tc.proportion, 218 | }; 219 | // TECHNICAL-DEBT should use deepCopy 220 | 221 | if (!sc) { 222 | // If the template component is missing from the source, add it. 223 | resultComponents.push(tcCopy); 224 | } else { 225 | // If present, expand it with values from the template. 226 | if (!sc.materialCountryId) 227 | throw new Error( 228 | "unexpected error: materialCountryId should have been filled with manufacturingCountryId" 229 | ); 230 | 231 | const resultComp: PartialProductDataEntityComponent = { 232 | componentId: tc.componentId, 233 | materials: (() => { 234 | if (sc.materials && sc.materials.length > 0) { 235 | // Source component already has materials, leave untouched 236 | return sc.materials; 237 | } else { 238 | return tcCopy.materials; 239 | } 240 | })(), 241 | materialCountryId: sc.materialCountryId, 242 | proportion: sc.proportion || tc.proportion, 243 | }; 244 | resultComponents.push(resultComp); 245 | } 246 | } 247 | return resultComponents; 248 | } 249 | 250 | export function sumProportions( 251 | items: { proportion?: number }[] | undefined 252 | ): number { 253 | if (!items) return 0.0; 254 | const sum = items.reduce((sum, item) => sum + (item.proportion || 0.0), 0.0); 255 | return sum; 256 | } 257 | 258 | // TECHNICAL-DEBT should be synchronized with `ProductDataEntity` 259 | // type. 260 | export function checkDataValidity(data: ProductDataEntity) { 261 | const sumComponentProportions = sumProportions(data.components); 262 | if (sumComponentProportions < 1.0 - FLOAT_ERROR_MARGIN) { 263 | throw new Error( 264 | `invalid data, sum of proportions of components < 1.0 (${sumComponentProportions})` 265 | ); 266 | } 267 | for (const c of data.components) { 268 | const sumMaterialProportions = sumProportions(c.materials); 269 | if (sumMaterialProportions < 1.0 - FLOAT_ERROR_MARGIN) { 270 | throw new Error( 271 | `invalid data, sum of proportions for materials of ${c.componentId} < 1.0 (${sumMaterialProportions})` 272 | ); 273 | } 274 | } 275 | } 276 | 277 | function distributionModeForCountry( 278 | geographicalAreaProvider: IGeographicalAreaProvider, 279 | countryId: string 280 | ): string { 281 | const ga = geographicalAreaProvider.getById(countryId); 282 | if (ga.type === "country") { 283 | switch (ga.parentId) { 284 | case "asia": 285 | return "intercontinental/default"; 286 | case "south-america": 287 | return "intercontinental/default"; 288 | case "europe": 289 | return "intracontinental/default"; 290 | case "africa": 291 | return "intercontinental/default"; 292 | } 293 | } 294 | throw new Error( 295 | `unexpected "${ga.type}" geographic area, expected "country" (${ga.id})` 296 | ); 297 | } 298 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /src/operations/expand-partial-product-data.test.ts: -------------------------------------------------------------------------------- 1 | import { PartialProductDataEntity } from "../entities/PartialProductDataEntity"; 2 | import { ProductDataEntity } from "../entities/ProductDataEntity"; 3 | import { ProductDataTemplateEntity } from "../entities/ProductDataTemplateEntity"; 4 | import { IGeographicalAreaProvider } from "../providers/geographical-area"; 5 | import { IProductDataTemplateProvider } from "../providers/product-data-template-provider"; 6 | import { ModelVersion } from "../types"; 7 | import { ExpandPartialProductDataEntityOperation } from "./expand-partial-product-data"; 8 | 9 | const testPartial_Empty: PartialProductDataEntity = {}; 10 | 11 | const testPartial_Complete: PartialProductDataEntity = { 12 | weight: 0.7, 13 | components: [ 14 | { 15 | componentId: "upper", 16 | materials: [ 17 | { 18 | materialId: "upperFromPartial", 19 | proportion: 1.0, 20 | }, 21 | ], 22 | materialCountryId: "upperMaterialCountryIdFromPartial", 23 | proportion: 0.3, 24 | }, 25 | { 26 | componentId: "insole", 27 | materials: [ 28 | { 29 | materialId: "insoleFromPartial", 30 | proportion: 1.0, 31 | }, 32 | ], 33 | materialCountryId: "insoleMaterialCountryIdFromPartial", 34 | proportion: 0.1, 35 | }, 36 | { 37 | componentId: "outsole", 38 | materials: [ 39 | { 40 | materialId: "outsoleFromPartial", 41 | proportion: 1.0, 42 | }, 43 | ], 44 | materialCountryId: "outsoleMaterialCountryIdFromPartial", 45 | proportion: 0.6, 46 | }, 47 | ], 48 | manufacturingCountryId: "countryIdFromPartial", 49 | endOfLifeOption: "endOfLifeOptionFromPartial", 50 | endOfLifeValue: null, 51 | }; 52 | 53 | const testTemplates: ProductDataTemplateEntity[] = [ 54 | { 55 | id: "template1", 56 | label: "label template1", 57 | source: "source template1", 58 | modelVersion: ModelVersion.version_0_2_0, 59 | productCategorySlug: "shoes/sneakers", 60 | weight: 1.0, 61 | components: [ 62 | { 63 | componentId: "upper", 64 | materials: [ 65 | { 66 | materialId: "upperFromTemplate1", 67 | proportion: 1.0, 68 | }, 69 | ], 70 | materialCountryId: "upperMaterialCountryIdFromTemplate1", 71 | proportion: 0.3, 72 | }, 73 | { 74 | componentId: "insole", 75 | materials: [ 76 | { 77 | materialId: "insoleFromTemplate1", 78 | proportion: 1.0, 79 | }, 80 | ], 81 | materialCountryId: "insoleMaterialCountryIdFromTemplate1", 82 | proportion: 0.1, 83 | }, 84 | { 85 | componentId: "outsole", 86 | materials: [ 87 | { 88 | materialId: "outsoleFromTemplate1", 89 | proportion: 1.0, 90 | }, 91 | ], 92 | materialCountryId: "outsoleMaterialCountryIdFromTemplate1", 93 | proportion: 0.5, 94 | }, 95 | ], 96 | manufacturingCountryId: "countryIdFromTemplate1", 97 | endOfLifeOption: "endOfLifeOptionFromTemplate1", 98 | endOfLifeValue: null, 99 | }, 100 | { 101 | id: "template2", 102 | label: "label template2", 103 | source: "source template2", 104 | modelVersion: ModelVersion.current, 105 | productCategorySlug: "shoes/sneakers", 106 | weight: 1.0, 107 | components: [ 108 | { 109 | componentId: "upper", 110 | materials: [ 111 | { 112 | materialId: "upperFromTemplate2", 113 | proportion: 1.0, 114 | }, 115 | ], 116 | materialCountryId: "upperMaterialCountryIdFromTemplate2", 117 | proportion: 0.3, 118 | }, 119 | ], 120 | manufacturingCountryId: "countryIdFromTemplate2", 121 | endOfLifeOption: "custom", 122 | endOfLifeValue: 0.9, 123 | }, 124 | ]; 125 | 126 | const templateProvider: ( 127 | template?: ProductDataTemplateEntity 128 | ) => IProductDataTemplateProvider = (template) => { 129 | return { 130 | get: (templateId: string) => { 131 | if (template) return template; 132 | const testTemplate = testTemplates.find(({ id }) => id === templateId); 133 | if (!testTemplate) 134 | throw new Error(`missing test template for id "${templateId}"`); 135 | return testTemplate; 136 | }, 137 | allIdAndLabel: () => { 138 | return []; 139 | }, // not used here 140 | }; 141 | }; 142 | 143 | const geographicalAreaProvider: IGeographicalAreaProvider = { 144 | getById: (id) => { 145 | if (id === "somewhere-in-europe") { 146 | return { 147 | type: "country", 148 | id: "somewhere-in-europe", 149 | label: "Somewhere in Europe", 150 | parentId: "europe", 151 | }; 152 | } 153 | return { 154 | id, 155 | label: "Country from Partial", 156 | type: "country", 157 | parentId: "asia", 158 | }; 159 | }, 160 | allCountries: () => { 161 | return []; 162 | }, 163 | }; 164 | 165 | const expandPartialProductData = ExpandPartialProductDataEntityOperation( 166 | templateProvider(), 167 | geographicalAreaProvider 168 | ); 169 | 170 | test("ExpandPartialProductDataEntity(Empty, any) fills all values without adding template specific fields: id, label, source)", () => { 171 | const partial: PartialProductDataEntity = { ...testPartial_Empty }; 172 | const result: ProductDataEntity = expandPartialProductData( 173 | partial, 174 | "template1", 175 | ModelVersion.current 176 | ); 177 | 178 | // Checking template's specific fields are removed. 179 | expect((result as any).id).toBeUndefined(); 180 | expect((result as any).label).toBeUndefined(); 181 | expect((result as any).source).toBeUndefined(); 182 | 183 | // Checking expanded fields. 184 | expect(result.weight).toBeCloseTo(1.0); 185 | expect(result.components[0].materials[0].materialId).toBe( 186 | "upperFromTemplate1" 187 | ); 188 | expect(result.components[0].materialCountryId).toBe( 189 | "upperMaterialCountryIdFromTemplate1" 190 | ); 191 | expect(result.components[0].proportion).toBeCloseTo(0.3); 192 | expect(result.components[1].materials[0].materialId).toBe( 193 | "insoleFromTemplate1" 194 | ); 195 | expect(result.components[1].materialCountryId).toBe( 196 | "insoleMaterialCountryIdFromTemplate1" 197 | ); 198 | expect(result.components[1].proportion).toBeCloseTo(0.1); 199 | expect(result.components[2].materials[0].materialId).toBe( 200 | "outsoleFromTemplate1" 201 | ); 202 | expect(result.components[2].materialCountryId).toBe( 203 | "outsoleMaterialCountryIdFromTemplate1" 204 | ); 205 | expect(result.components[2].proportion).toBeCloseTo(0.5); 206 | expect(result.manufacturingCountryId).toBe("countryIdFromTemplate1"); 207 | 208 | expect(result.endOfLifeRecyclingProgram).toBeUndefined(); 209 | expect(result.endOfLifeOption).toBe("endOfLifeOptionFromTemplate1"); 210 | expect(result.endOfLifeValue).toBeNull(); 211 | }); 212 | 213 | test("ExpandPartialProductDataEntity(blankWeight, any) overwrites weight", () => { 214 | let partial: PartialProductDataEntity = { 215 | ...testPartial_Empty, 216 | }; 217 | 218 | for (const value of [NaN, undefined]) { 219 | partial.weight = value; 220 | expect( 221 | expandPartialProductData(partial, "template1", ModelVersion.current) 222 | .weight 223 | ).toBeCloseTo(1); 224 | } 225 | }); 226 | 227 | test("ExpandPartialProductDataEntity(completeData, any, current) changes no value", () => { 228 | const partial: PartialProductDataEntity = { 229 | ...testPartial_Complete, 230 | }; 231 | const result: ProductDataEntity = expandPartialProductData( 232 | partial, 233 | "template1", 234 | ModelVersion.current 235 | ); 236 | 237 | expect(result.weight).toBeCloseTo(testPartial_Complete.weight!); 238 | for (let i = 0; i < result.components.length; i++) { 239 | expect(result.components[i]).toMatchObject( 240 | testPartial_Complete.components![i] 241 | ); 242 | } 243 | expect(result.manufacturingCountryId).toBe( 244 | testPartial_Complete.manufacturingCountryId 245 | ); 246 | expect(result.endOfLifeRecyclingProgram).toBe( 247 | testPartial_Complete.endOfLifeRecyclingProgram 248 | ); 249 | }); 250 | 251 | test("ExpandPartialProductDataEntity(partialData, any, current) expands missing values, adding missing components and completing partial components", () => { 252 | const partial: PartialProductDataEntity = { 253 | weight: 0.6, 254 | components: [ 255 | { 256 | componentId: "insole", 257 | proportion: 0.1, 258 | }, 259 | { 260 | componentId: "outsole", 261 | materials: [ 262 | { 263 | materialId: "outsoleFromPartial", 264 | proportion: 1.0, 265 | }, 266 | ], 267 | }, 268 | ], 269 | endOfLifeRecyclingProgram: true, 270 | }; 271 | 272 | const result: ProductDataEntity = expandPartialProductData( 273 | partial, 274 | "template1", 275 | ModelVersion.current 276 | ); 277 | 278 | expect(result.weight).toBeCloseTo(partial.weight!); // unchanged 279 | expect(result.components.length).toBe(4); // added 1 from template and 1 other 280 | ["upper", "insole", "outsole"].map((componentId) => { 281 | expect(result.components.map((c) => c.componentId)).toContain(componentId); 282 | }); 283 | 284 | let expandedComponent = result.components.find( 285 | (c) => c.componentId === "insole" 286 | ); 287 | expect(expandedComponent!.materials[0].materialId).toBe( 288 | "insoleFromTemplate1" 289 | ); // expanded 290 | expect(expandedComponent!.materials[0].proportion).toBeCloseTo(1.0); // expanded 291 | expect(expandedComponent!.proportion).toBeCloseTo(0.1); // unchanged 292 | 293 | expandedComponent = result.components.find( 294 | (c) => c.componentId === "outsole" 295 | ); 296 | expect(expandedComponent!.materials[0].materialId).toBe("outsoleFromPartial"); // expanded 297 | expect(expandedComponent!.materials[0].proportion).toBeCloseTo(1.0); // expanded 298 | expect(expandedComponent!.proportion).toBeCloseTo(0.5); // unchanged 299 | 300 | const addedComponent = result.components.find( 301 | (c) => c.componentId === "upper" 302 | ); 303 | expect(addedComponent!.materials[0].materialId).toBe("upperFromTemplate1"); 304 | expect(addedComponent!.materials[0].proportion).toBeCloseTo(1.0); 305 | expect(addedComponent!.proportion).toBeCloseTo(0.3); 306 | }); 307 | 308 | test("ExpandPartialProductDataEntity(partial, any, current) expands the material country id from the partial's manufacturing country, without overridding it with the template's value", () => { 309 | const partial: PartialProductDataEntity = { 310 | weight: 0.7, 311 | components: [ 312 | { 313 | componentId: "upper", // TECHNICAL-DEBT this was not mandatory in schema v0.1.0 314 | materials: [ 315 | { 316 | materialId: "upperFromPartial", 317 | proportion: 1.0, 318 | }, 319 | ], 320 | proportion: 0.3, 321 | }, 322 | { 323 | componentId: "outsole", 324 | materials: [ 325 | { 326 | materialId: "outsoleFromPartial", 327 | proportion: 1.0, 328 | }, 329 | ], 330 | proportion: 0.3, 331 | }, 332 | ], 333 | manufacturingCountryId: "countryFromPartial", 334 | endOfLifeRecyclingProgram: true, 335 | }; 336 | 337 | const result = expandPartialProductData( 338 | partial, 339 | "template1", 340 | ModelVersion.current 341 | ); 342 | 343 | let component = result.components.find((c) => c.componentId === "upper"); 344 | expect(component!.materialCountryId).toEqual("countryFromPartial"); 345 | 346 | component = result.components.find((c) => c.componentId === "outsole"); 347 | expect(component!.materialCountryId).toEqual("countryFromPartial"); 348 | }); 349 | 350 | test("ExpandPartialProductDataEntity(partial, any, current) expand unknown material proportions following implicit data expansion rules", () => { 351 | const partial: PartialProductDataEntity = { 352 | components: [ 353 | { 354 | componentId: "upper", 355 | materials: [ 356 | { 357 | materialId: "upperFromPartial1", 358 | }, 359 | { 360 | materialId: "upperFromPartial2", 361 | }, 362 | ], 363 | proportion: 0.3, 364 | }, 365 | { 366 | componentId: "outsole", 367 | materials: [ 368 | { 369 | materialId: "outsoleFromPartial1", 370 | proportion: 0.5, 371 | }, 372 | { 373 | materialId: "outsoleFromPartial2", 374 | }, 375 | { 376 | materialId: "outsoleFromPartial3", 377 | }, 378 | ], 379 | proportion: 0.3, 380 | }, 381 | ], 382 | }; 383 | 384 | const result = expandPartialProductData( 385 | partial, 386 | "template1", 387 | ModelVersion.current 388 | ); 389 | 390 | let component = result.components.find((c) => c.componentId === "upper"); 391 | let material = component!.materials.find( 392 | (m) => m.materialId === "upperFromPartial1" 393 | ); 394 | expect(material!.proportion).toEqual(0.5); 395 | material = component!.materials.find( 396 | (m) => m.materialId === "upperFromPartial2" 397 | ); 398 | expect(material!.proportion).toEqual(0.5); 399 | 400 | component = result.components.find((c) => c.componentId === "outsole"); 401 | material = component!.materials.find( 402 | (m) => m.materialId === "outsoleFromPartial1" 403 | ); 404 | expect(material!.proportion).toEqual(0.5); 405 | material = component!.materials.find( 406 | (m) => m.materialId === "outsoleFromPartial2" 407 | ); 408 | expect(material!.proportion).toEqual(0.25); 409 | material = component!.materials.find( 410 | (m) => m.materialId === "outsoleFromPartial3" 411 | ); 412 | expect(material!.proportion).toEqual(0.25); 413 | }); 414 | 415 | test("ExpandPartialProductDataEntity(partial, any, current) expands missing component materialCountryId from partial's manufacturingCountryId when present", () => { 416 | const partial: PartialProductDataEntity = { 417 | components: [ 418 | { 419 | componentId: "upper", 420 | materials: [ 421 | { 422 | materialId: "upperFromPartial", 423 | }, 424 | ], 425 | proportion: 0.3, 426 | }, 427 | ], 428 | manufacturingCountryId: "countryIdFromPartial", 429 | }; 430 | 431 | const result = expandPartialProductData( 432 | partial, 433 | "template2", 434 | ModelVersion.current 435 | ); 436 | 437 | let component = result.components.find((c) => c.componentId === "upper"); 438 | expect(component!.materialCountryId).toBe("countryIdFromPartial"); 439 | }); 440 | 441 | test("ExpandPartialProductDataEntity(partial, any, current) expands missing component materialCountryId from template when partial is missing `manufacturingCountryId`", () => { 442 | const partial: PartialProductDataEntity = { 443 | components: [ 444 | { 445 | componentId: "upper", 446 | materials: [ 447 | { 448 | materialId: "upperFromPartial", 449 | }, 450 | ], 451 | proportion: 0.3, 452 | }, 453 | ], 454 | }; 455 | 456 | const result = expandPartialProductData( 457 | partial, 458 | "template2", 459 | ModelVersion.current 460 | ); 461 | 462 | let component = result.components.find((c) => c.componentId === "upper"); 463 | expect(component!.materialCountryId).toBe("countryIdFromTemplate2"); 464 | }); 465 | 466 | test("ExpandPartialProductDataEntity(partial, any, current) expands missing material proportions", () => { 467 | const partial: PartialProductDataEntity = { 468 | components: [ 469 | { 470 | componentId: "upper", 471 | materials: [ 472 | { 473 | materialId: "upperFromPartial", 474 | }, 475 | ], 476 | proportion: 0.3, 477 | }, 478 | ], 479 | }; 480 | 481 | const result = expandPartialProductData( 482 | partial, 483 | "template2", 484 | ModelVersion.current 485 | ); 486 | 487 | let component = result.components.find((c) => c.componentId === "upper"); 488 | let material = component!.materials.find( 489 | (m) => m.materialId === "upperFromPartial" 490 | ); 491 | expect(material!.proportion).toBeCloseTo(1.0); 492 | }); 493 | 494 | test('ExpandPartialProductDataEntity(partial, any, current) adds an "other" component to complete to 100% components\' proportions', () => { 495 | const partial: PartialProductDataEntity = { 496 | components: [ 497 | { 498 | componentId: "upper", 499 | materials: [ 500 | { 501 | materialId: "upperFromPartial", 502 | }, 503 | ], 504 | proportion: 0.3, 505 | }, 506 | ], 507 | }; 508 | 509 | const result = expandPartialProductData( 510 | partial, 511 | "template2", 512 | ModelVersion.current 513 | ); 514 | 515 | expect(result.components.length).toBe(2); // 1 original, 1 added by expansion 516 | 517 | let component = result.components.find((c) => c.componentId === "other"); 518 | expect(component!.proportion).toBeCloseTo(0.7); 519 | expect(component!.materialCountryId).toBe("countryIdFromTemplate2"); 520 | }); 521 | 522 | /** 523 | * Expansion of `distributionMode` 524 | * 525 | * Rules: 526 | * - Present: keep untouched. 527 | * - Missing: 528 | * - If `manufacturingCountryId` in Asia (`vietnam` or `china` as of now): set with `intercontinental/default`. 529 | * - Otherwise: set with `intracontinental/default`. 530 | */ 531 | 532 | test("expand(partialWithoutDistributionModeAndManufacturingCountryInAsia, any, any) sets distributionMode to `intercontinental/default`", () => { 533 | const result = expandPartialProductData( 534 | testPartial_Empty, 535 | "template1", // any (expansion not based on template) 536 | ModelVersion.current // any (expansion not based on template) 537 | ); 538 | expect(result.distributionMode).toBe("intercontinental/default"); 539 | }); 540 | 541 | test("expand(partialWithoutDistributionModeAndManufacturingCountryInEurope, any, any) sets distributionMode to `intracontinental/default`", () => { 542 | const partial = { 543 | ...testPartial_Empty, 544 | manufacturingCountryId: "somewhere-in-europe", 545 | }; 546 | const result = expandPartialProductData( 547 | partial, 548 | "template1", // any (expansion not based on template) 549 | ModelVersion.current // any (expansion not based on template) 550 | ); 551 | expect(result.distributionMode).toBe("intracontinental/default"); 552 | }); 553 | 554 | test("expand(partialWithDistributionMode, any, any) leaves distributionMode unchanged", () => { 555 | const partial = { 556 | ...testPartial_Empty, 557 | distributionMode: "some/mode", 558 | }; 559 | const result = expandPartialProductData( 560 | partial, 561 | "template1", // any (expansion not based on template) 562 | ModelVersion.current // any (expansion not based on template) 563 | ); 564 | expect(result.distributionMode).toBe("some/mode"); 565 | }); 566 | 567 | test("expand(emptyPartial, templateWithEndOfLifeOption...) uses template's endOfLifeOption and sets endOfLifeValue to null", () => { 568 | const result = expandPartialProductData( 569 | testPartial_Empty, 570 | "template1", 571 | ModelVersion.current 572 | ); 573 | expect(result.endOfLifeOption).toBe("endOfLifeOptionFromTemplate1"); 574 | expect(result.endOfLifeValue).toBeNull(); 575 | }); 576 | 577 | test("expand(emptyPartial, templateWithEndOfLifeCustomValue...) uses template's endOfLifeValue", () => { 578 | const result = expandPartialProductData( 579 | testPartial_Empty, 580 | "template2", 581 | ModelVersion.current 582 | ); 583 | expect(result.endOfLifeOption).toBe("custom"); 584 | expect(result.endOfLifeValue).toBeCloseTo(0.9); 585 | }); 586 | 587 | test("expand(partialWithEndOfLifeOption...) leaves endOfLifeOption unchanged", () => { 588 | for (const templateId of testTemplates.map((tt) => tt.id)) { 589 | const result = expandPartialProductData( 590 | testPartial_Complete, 591 | templateId, 592 | ModelVersion.current 593 | ); 594 | expect(result.endOfLifeOption).toBe("endOfLifeOptionFromPartial"); 595 | expect(result.endOfLifeValue).toBeNull(); 596 | } 597 | }); 598 | 599 | test("expand(partialWithEndOfLifeCustomValue...) leaves endOfLife fields unchanged", () => { 600 | for (const templateId of testTemplates.map((tt) => tt.id)) { 601 | const partial: PartialProductDataEntity = { 602 | ...testPartial_Complete, 603 | endOfLifeOption: "custom", 604 | endOfLifeValue: 7.7, 605 | }; 606 | const result = expandPartialProductData( 607 | partial, 608 | templateId, 609 | ModelVersion.current 610 | ); 611 | expect(result.endOfLifeOption).toBe("custom"); 612 | expect(result.endOfLifeValue).toBeCloseTo(7.7); 613 | } 614 | }); 615 | 616 | test('expand(partialWithEndOfLifeOptionCustomAndEmptyValue...) throws an error because value must be set if option is "custom"', () => { 617 | const partial: PartialProductDataEntity = { 618 | ...testPartial_Complete, 619 | endOfLifeOption: "custom", 620 | endOfLifeValue: null, 621 | }; 622 | expect(() => { 623 | expandPartialProductData(partial, "template1", ModelVersion.current); 624 | }).toThrow("unexpected null endOfLifeValue with option=custom"); 625 | }); 626 | 627 | // DEPRECATED for retro-compatibility before new endOfLife properties 628 | test('expand(legacyPartialWithEndOfLifeRecyclingProgram, any, current) sets option to "with..." or "withoutRecyclingProgram" and value to null', () => { 629 | for (const testCase of [ 630 | { boolValue: true, expectedOption: "withRecyclingProgram" }, 631 | { boolValue: false, expectedOption: "withoutRecyclingProgram" }, 632 | ]) { 633 | for (const templateId of testTemplates.map((tt) => tt.id)) { 634 | const partial: PartialProductDataEntity = { 635 | ...testPartial_Empty, 636 | endOfLifeRecyclingProgram: testCase.boolValue, 637 | }; 638 | const result = expandPartialProductData( 639 | partial, 640 | templateId, 641 | ModelVersion.current 642 | ); 643 | expect(result.endOfLifeOption).toBe(testCase.expectedOption); 644 | } 645 | } 646 | }); 647 | // expand the `option` with the correct value based on `endOfLifeRecyclingProgram` if present 648 | 649 | // throws if ...RecyclingProgram is present with option and value 650 | test("expand(partialWithLegacyAndNewEndOfLifeValues...) throws an error because only legacy or new values should be set", () => { 651 | const partial: PartialProductDataEntity = { 652 | ...testPartial_Complete, 653 | endOfLifeRecyclingProgram: true, 654 | }; 655 | expect(() => { 656 | expandPartialProductData(partial, "template1", ModelVersion.current); 657 | }).toThrow("invalid partial with both legacy and new values for `endOfLife`"); 658 | }); 659 | -------------------------------------------------------------------------------- /src/data/parameters.ts: -------------------------------------------------------------------------------- 1 | import { ModelVersion } from "../types"; 2 | import { ModelParameter } from "../legacy/types"; 3 | 4 | export const modelParameters: ModelParameter[] = [ 5 | { 6 | id: "emissionFactor/material/cotton/standard", 7 | label: "Cotton, conventional", 8 | source: 9 | "Using value for 'Cotton - Conventional' for China from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+conventional&refine.impact_country=China). 2.100 (Crop farming) + 0.871 (Ginning) + 13.246 (Spinning Weaving and Dyeing): 6.945.", 10 | value: 16.217, 11 | unit: "kgCO2eq/kg", 12 | countryIds: ["china"], 13 | version: ModelVersion.version_0_2_0, 14 | }, 15 | { 16 | id: "emissionFactor/material/cotton/standard", 17 | label: "Cotton - Conventional", 18 | source: 19 | "Using value for 'Cotton - Conventional' for Portugal from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+conventional&refine.impact_country=Portugal). 2.282 (Crop farming) + 0.383 (Ginning) + 4.280 (Spinning Weaving and Dyeing): 6.945.", 20 | value: 6.945, 21 | unit: "kgCO2eq/kg", 22 | countryIds: ["portugal"], 23 | version: ModelVersion.version_0_3_0, 24 | }, 25 | { 26 | id: "emissionFactor/material/cotton/standard", 27 | label: "Cotton - Conventional", 28 | source: 29 | "Using value for 'Cotton - Conventional' for Vietnam from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+conventional&refine.impact_country=Vietnam). 5.583 (Crop farming) + 0.640 (Ginning) + 13.246 (Spinning Weaving and Dyeing for China): 19.469.", 30 | value: 19.469, 31 | unit: "kgCO2eq/kg", 32 | countryIds: ["vietnam"], 33 | version: ModelVersion.version_0_5_0, 34 | comments: 35 | "No value for 'Spinning Weaving and Dyeing' step in Vietnam, using China's.", 36 | }, 37 | { 38 | id: "emissionFactor/material/cotton/standard", 39 | label: "Cotton - Conventional", 40 | source: 41 | "Using value for 'Cotton - Conventional' for Peru from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+conventional&refine.impact_country=Peru). 3.147 (Crop farming) + 0.539 (Ginning) + 13.246 (Spinning Weaving and Dyeing for China): 16.932", 42 | value: 16.932, 43 | unit: "kgCO2eq/kg", 44 | countryIds: ["peru"], 45 | version: ModelVersion.version_0_5_0, 46 | comments: 47 | "No value for 'Spinning Weaving and Dyeing' step in Peru, using China's.", 48 | }, 49 | { 50 | id: "emissionFactor/material/cotton/organic", 51 | label: "Cotton - Organic", 52 | source: 53 | "Kering database, Raw Material Intensities 2020: https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/", 54 | value: 4.356, 55 | unit: "kgCO2eq/kg", 56 | countryIds: ["italy"], 57 | version: ModelVersion.version_0_2_0, 58 | }, 59 | { 60 | id: "emissionFactor/material/cotton/organic", 61 | label: "Cotton - Organic", 62 | source: 63 | "[Kering Group database](https://kering-group.opendatasoft.com) for 'Cotton - Organic':\n- Crop farming: 1.447 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton&refine.impact_country=Spain&refine.processstep=Crop+farming))\n- Ginning: 0.491 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+organic&refine.impact_country=Spain&refine.processstep=Ginning))\n- Spinning, weaving and dyeing: 2.652 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+organic&refine.impact_country=Spain&refine.processstep=Spinning+Weaving+and+Dyeing))", 64 | value: 4.59, 65 | unit: "kgCO2eq/kg", 66 | countryIds: ["spain"], 67 | version: ModelVersion.version_0_3_0, 68 | }, 69 | { 70 | id: "emissionFactor/material/cotton/organic", 71 | label: "Cotton - Organic", 72 | source: 73 | "[Kering Group database](https://kering-group.opendatasoft.com) for 'Cotton - Organic':\n- Crop farming (no value for Portugal, using value for Spain): 1.447 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+organic&refine.processstep=Crop+farming&refine.impact_country=Spain))\n- Ginning (no value for Portugal, using value for Spain): 0.491 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+organic&refine.impact_country=Spain&refine.processstep=Ginning))\n- Spinning, weaving and dyeing: 2.668 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+organic&refine.impact_country=Portugal))", 74 | value: 4.606, 75 | unit: "kgCO2eq/kg", 76 | countryIds: ["portugal"], 77 | version: ModelVersion.version_0_3_0, 78 | }, 79 | { 80 | id: "emissionFactor/material/cotton/organic", 81 | label: "Cotton - Organic", 82 | source: 83 | "Using value 'Cotton - Organic' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+organic&refine.impact_country=China) for China: 1.257 (Crop Farming) + 0.855 (Ginning) + 13.246 (Spinning, Weaving & Dyeing) = 15.358", 84 | value: 15.358, 85 | unit: "kgCO2eq/kg", 86 | countryIds: ["china"], 87 | version: ModelVersion.version_0_4_0, 88 | }, 89 | { 90 | id: "emissionFactor/material/cotton/organic", 91 | label: "Cotton - Organic", 92 | source: 93 | "Using value for 'Cotton - Organic' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cotton+-+organic), averaging several values for different countries. 1.92925 (Crop farming, average for 4 available african countries: Tanzania - 3.048, Zimbabwe - 0.811, Central African Republic - 3.048, Cameroun - 0.810) + 0.683 (Ginning, same value for all 4 african countries) + 13.246 (Spinning Weaving and Dyeing, using conservative China's value in the absence of any value for african countries): 15.85825", 94 | value: 15.85825, 95 | unit: "kgCO2eq/kg", 96 | countryIds: ["senegal"], 97 | version: ModelVersion.version_0_5_0, 98 | comments: 99 | "Using an average value for 4 other african countries. Fallback on China's value for 'Spinning Weaving and Dyeing' process step.", 100 | }, 101 | { 102 | id: "emissionFactor/material/cotton/recycled", 103 | label: "Cotton, recycled", 104 | source: 105 | "Kering database, Raw Material Intensities 2020: https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/", 106 | value: 4.265, 107 | unit: "kgCO2eq/kg", 108 | countryIds: ["spain"], 109 | version: ModelVersion.version_0_2_0, 110 | }, 111 | { 112 | id: "emissionFactor/material/cotton/recycled", 113 | label: "Cotton, recycled", 114 | source: 115 | "Kering database, Raw Material Intensities 2020: https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/", 116 | value: 4.28, 117 | unit: "kgCO2eq/kg", 118 | countryIds: ["portugal"], 119 | version: ModelVersion.version_0_2_0, 120 | }, 121 | { 122 | id: "emissionFactor/material/elastane", 123 | label: "Elastane", 124 | source: 125 | "Using value for 'Plastic - Elastane' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?q=elastane&refine.environmental_impact_group=GHGs): 0.297 (Extraction, China) + 20.644 (Processing, China) + 4.638 (Spininng, Weaving and Dyeing, Italy) = 25.579", 126 | value: 25.579, 127 | unit: "kgCO2eq/kg", 128 | countryIds: ["china"], 129 | version: ModelVersion.version_0_4_0, 130 | comments: 131 | "Fallback on value for 'italy' for the 'Spinning, Weaving and Dyeing' step.", 132 | }, 133 | { 134 | id: "emissionFactor/material/linen/organic", 135 | label: "Linen, organic", 136 | source: 137 | "'Linen - organic' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?q=linen+-+organic&refine.environmental_impact_group=GHGs): 0.914 (Crop farming, using value for Belgium - none available for Portugal) + 0.298 (Ginning, using value for Belgium - none available for Portugal) + 4.356 (Spinning, Weaving and Dyeing, for Portugal) = 5.492", 138 | value: 5.568, 139 | unit: "kgCO2eq/kg", 140 | countryIds: ["italy"], 141 | version: ModelVersion.version_0_4_0, 142 | }, 143 | { 144 | id: "emissionFactor/material/linen/standard", 145 | label: "Linen - Standard", 146 | source: 147 | "'Linen - conventional' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?q=linen+-+organic&refine.environmental_impact_group=GHGs): 0.914 (Crop farming, using value for Belgium - none available for Portugal) + 0.298 (Ginning, using value for Belgium - none available for Portugal) + 4.280 (Spinning, Weaving and Dyeing, for Portugal) = 5.492", 148 | value: 5.492, 149 | unit: "kgCO2eq/kg", 150 | countryIds: ["portugal"], 151 | version: ModelVersion.version_0_4_0, 152 | }, 153 | { 154 | id: "emissionFactor/material/linen/organic", 155 | label: "Linen, organic", 156 | source: 157 | "'Linen - organic' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?q=linen+-+organic&refine.environmental_impact_group=GHGs): 0.914 (Crop farming, using value for Belgium - none available for Portugal) + 0.298 (Ginning, using value for Belgium - none available for Portugal) + 4.280 (Spinning, Weaving and Dyeing, for Portugal) = 5.492", 158 | value: 5.492, 159 | unit: "kgCO2eq/kg", 160 | countryIds: ["portugal"], 161 | version: ModelVersion.version_0_4_0, 162 | }, 163 | { 164 | id: "emissionFactor/material/polyester/standard", 165 | label: "Polyester - Standard", 166 | source: 167 | "Using value for 'Polyester - Conventional' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyester+-+conventional). 0.415 (Extraction) + 14.217 (Processing) + 14.697 (Spinning Weaving and Dyeing) = 29.329.", 168 | value: 29.329, 169 | unit: "kgCO2eq/kg", 170 | countryIds: ["china"], 171 | version: ModelVersion.version_0_5_0, 172 | }, 173 | { 174 | id: "emissionFactor/material/polyester/standard", 175 | label: "Polyester - Standard", 176 | source: 177 | "Using value for 'Polyester - Conventional' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyester+-+conventional). 0.203 (Extraction) + 3.794 (Processing) + 4.546 (Spinning Weaving and Dyeing) = 29.329.", 178 | value: 8.543, 179 | unit: "kgCO2eq/kg", 180 | countryIds: ["spain"], 181 | version: ModelVersion.version_0_5_0, 182 | }, 183 | { 184 | id: "emissionFactor/material/polyester/standard", 185 | label: "Polyester - Standard", 186 | source: 187 | "Using value for 'Polyester - Conventional' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyester+-+conventional). 0.203 (Extraction) + 3.794 (Processing) + 4.638 (Spinning Weaving and Dyeing) = 29.329.", 188 | value: 8.635, 189 | unit: "kgCO2eq/kg", 190 | countryIds: ["italy"], 191 | version: ModelVersion.version_0_5_0, 192 | }, 193 | { 194 | id: "emissionFactor/material/polyester/standard", 195 | label: "Polyester - Standard", 196 | source: 197 | "Using value for 'Polyester - Conventional' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyester+-+conventional). 0.203 (Extraction, value for Spain) + 3.794 (Processing, value for Spain) + 4.561 (Spinning Weaving and Dyeing) = 8.558.", 198 | value: 8.558, 199 | unit: "kgCO2eq/kg", 200 | countryIds: ["portugal"], 201 | version: ModelVersion.version_0_5_0, 202 | comments: 203 | "Using values for Spain for 2 process steps (missing values for Portugal).", 204 | }, 205 | { 206 | id: "emissionFactor/material/polyester/standard", 207 | label: "Polyester - Standard", 208 | source: 209 | "Using value for 'Polyester - Conventional' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyester+-+conventional). 0.203 (Extraction, value for Greece) + 3.794 (Processing, value for Greece) + 4.50075 (Spinning Weaving and Dyeing, average value for other european countries, Italy - 4.638, Portugal - 4.561, Spain - 4.546, France - 4.163, Germany - 4.832, Switzerland - 4.129, UK - 4.764, Belgium - 4.373) = 8.49775.", 210 | value: 8.49775, 211 | unit: "kgCO2eq/kg", 212 | countryIds: ["greece"], 213 | version: ModelVersion.version_0_5_0, 214 | comments: 215 | "Using average value for european countries with data for 'Spinning Weaving and Dyeing' step.", 216 | }, 217 | { 218 | id: "emissionFactor/material/polyester/standard", 219 | label: "Polyester - Standard", 220 | source: 221 | "Using value for 'Polyester - Conventional' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyester+-+conventional). 0.217 (Extraction) + 13.311 (Processing) + 14.697 (Spinning Weaving and Dyeing, value for China) = 28.225.", 222 | value: 28.225, 223 | unit: "kgCO2eq/kg", 224 | countryIds: ["vietnam"], 225 | version: ModelVersion.version_0_5_0, 226 | comments: "Using China's value for 'Spinning Weaving and Dyeing'.", 227 | }, 228 | { 229 | id: "emissionFactor/material/polyester/recycled", 230 | label: "Polyester - Recycled", 231 | source: 232 | "Using value for 'Polyester - recycled' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyester+-+recycled). 3.205 (Processing) + 4.638 (Spinning Weaving and Dyeing) = 7.843", 233 | value: 7.843, 234 | unit: "kgCO2eq/kg", 235 | countryIds: ["italy"], 236 | version: ModelVersion.version_0_5_0, 237 | }, 238 | { 239 | id: "emissionFactor/material/polyester/recycled", 240 | label: "Polyester - Recycled", 241 | source: 242 | "Using value for 'Polyester - recycled' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyester+-+recycled). 3.205 (Processing, missing vlaue for Spain, using Italy's) + 4.546 (Spinning Weaving and Dyeing, for Spain) = 7.751", 243 | value: 7.751, 244 | unit: "kgCO2eq/kg", 245 | countryIds: ["spain"], 246 | version: ModelVersion.version_0_5_0, 247 | comments: 248 | "Fallback on Italy's value for 'Spinning Weaving and Dyeing' step.", 249 | }, 250 | { 251 | id: "emissionFactor/material/polyester/recycled", 252 | label: "Polyester - Recycled", 253 | source: 254 | "Using value for 'Polyester - recycled' from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyester+-+recycled). 3.205 (Processing, missing vlaue for Spain, using Italy's) + 4.561 (Spinning Weaving and Dyeing, for Portugal) = 7.766", 255 | value: 7.766, 256 | unit: "kgCO2eq/kg", 257 | countryIds: ["portugal"], 258 | version: ModelVersion.version_0_5_0, 259 | comments: 260 | "Fallback on Italy's value for 'Spinning Weaving and Dyeing' step.", 261 | }, 262 | { 263 | id: "emissionFactor/material/polyester/recycled", 264 | label: "Polyester - Recycled", 265 | source: 266 | "Kering database, Raw Material Intensities 2020: https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/", 267 | value: 26.676, 268 | unit: "kgCO2eq/kg", 269 | countryIds: ["china", "vietnam"], 270 | version: ModelVersion.version_0_5_0, 271 | comments: "Using China's value for other asian countries: Vietnam.", 272 | }, 273 | { 274 | id: "emissionFactor/material/rubber/undetermined", 275 | label: "Rubber", 276 | source: "Using the value for synthetic rubber for China", 277 | value: 5.844, 278 | unit: "kgCO2eq/kg", 279 | countryIds: ["china", "senegal"], 280 | version: ModelVersion.version_0_3_0, 281 | comments: 282 | "Fallback on material/rubber/synthetic. Using the more conservative 'synthetic' value, but could be improved by determining the mean distribution of natural/synthetic rubber in shoes. Using China's value for other countries: Senegal.", 283 | }, 284 | { 285 | id: "emissionFactor/material/rubber/undetermined", 286 | label: "Rubber", 287 | source: 288 | "Using the value for synthetic rubber for european countries (from Spain's value).", 289 | value: 1.529, 290 | unit: "kgCO2eq/kg", 291 | countryIds: ["spain", "portugal", "italy", "greece", "france"], 292 | version: ModelVersion.version_0_3_0, 293 | comments: 294 | "Fallback on material/rubber/synthetic. Using the more conservative 'synthetic' value, but could be improved by determining the mean distribution of natural/synthetic rubber in shoes. Needs improvement: we're using Spain's value which is the only one available for 'rubber - synthetic' in an european country.", 295 | }, 296 | { 297 | id: "emissionFactor/material/rubber/undetermined", 298 | label: "Rubber", 299 | source: "Using our 'rubber/synthetic' value for Vietnam.", 300 | value: 4.985, 301 | unit: "kgCO2eq/kg", 302 | countryIds: ["vietnam"], 303 | version: ModelVersion.version_0_5_0, 304 | comments: 305 | "Fallback on 'rubber/synthetic'. Using the more conservative 'synthetic' value, but could be improved by determining the mean distribution of natural/synthetic rubber in shoes.", 306 | }, 307 | { 308 | id: "emissionFactor/material/rubber/natural", 309 | label: "Rubber - Natural", 310 | source: 311 | "Using value for 'Rubber - Natural' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=rubber+-+natural). 0.021 (Production) + 0.513 (Processing) = 0.534.", 312 | value: 0.534, 313 | unit: "kgCO2eq/kg", 314 | countryIds: ["china", "vietnam", "portugal", "spain", "italy", "peru"], 315 | version: ModelVersion.version_0_5_0, 316 | comments: "Using value for China for all countries.", 317 | }, 318 | { 319 | id: "emissionFactor/material/rubber/synthetic", 320 | label: "Rubber - Synthetic", 321 | source: 322 | "Value from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=rubber+-+synthetic&refine.impact_country=China) for 'Rubber - synthetic' and China: 0.449 (Extraction) + 5.395 (Processing): 5.844", 323 | value: 5.844, 324 | unit: "kgCO2eq/kg", 325 | countryIds: ["china", "senegal", "peru"], 326 | version: ModelVersion.version_0_5_0, 327 | comments: "Using China's value for other countries: Senegal, Peru", 328 | }, 329 | { 330 | id: "emissionFactor/material/rubber/synthetic", 331 | label: "Rubber - Synthetic", 332 | source: 333 | "Value from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=rubber+-+synthetic&refine.impact_country=Vietnam) for 'Rubber - synthetic' and Vietnam: 0.449 (Extraction) + 4.536 (Processing): 4.985", 334 | value: 4.985, 335 | unit: "kgCO2eq/kg", 336 | countryIds: ["vietnam"], 337 | version: ModelVersion.version_0_5_0, 338 | }, 339 | { 340 | id: "emissionFactor/material/rubber/synthetic", 341 | label: "Rubber - Synthetic", 342 | source: 343 | "Value from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=rubber+-+synthetic&refine.impact_country=Spain) for 'Rubber - synthetic' and Spain: 0.112 (Extraction) + 1.417 (Processing): 1.529", 344 | value: 1.529, 345 | unit: "kgCO2eq/kg", 346 | countryIds: ["spain", "portugal", "italy", "greece", "france"], 347 | version: ModelVersion.version_0_5_0, 348 | comments: 349 | "Using the value for Spain for other european countries: Portugal, Italy, Greece, France.", 350 | }, 351 | { 352 | id: "emissionFactor/material/rubber/recycled", 353 | label: "Rubber, recycled", 354 | source: "Using the value for synthetic rubber for Spain", 355 | value: 1.529, 356 | unit: "kgCO2eq/kg", 357 | countryIds: ["portugal"], 358 | version: ModelVersion.version_0_2_0, 359 | }, 360 | { 361 | id: "emissionFactor/material/rubber/recycled", 362 | label: "Rubber, recycled", 363 | source: "Using the value for synthetic rubber for China", 364 | value: 5.844, 365 | unit: "kgCO2eq/kg", 366 | countryIds: ["china"], 367 | version: ModelVersion.version_0_2_0, 368 | }, 369 | { 370 | id: "emissionFactor/material/rubber/recycled", 371 | label: "Rubber, recycled", 372 | source: "Using the value for synthetic rubber for Spain", 373 | value: 1.529, 374 | unit: "kgCO2eq/kg", 375 | countryIds: ["spain"], 376 | version: ModelVersion.version_0_2_0, 377 | }, 378 | { 379 | id: "emissionFactor/material/rubber/recycled", 380 | label: "Rubber, recycled", 381 | source: 382 | "Using 'Processing' step for 'Rubber - synthetic' in Vietnam from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?q=rubber&refine.impact_country=Vietnam&refine.environmental_impact_group=GHGs) in the absence of value for recycled rubber. The 'extraction' step is ignored since it's recycled material.", 383 | value: 4.536, 384 | unit: "kgCO2eq/kg", 385 | countryIds: ["vietnam"], 386 | version: ModelVersion.version_0_4_0, 387 | }, 388 | { 389 | id: "emissionFactor/material/nylon/standard", 390 | label: "Nylon", 391 | source: 392 | "Value for 'Synthetic Fibers - Nylon - Conventional' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?q=nylon+-+conventional&refine.raw_material=Synthetic+Fibers+&refine.environmental_impact_group=GHGs&refine.impact_country=China) for China: 0.951 (Extraction) + 66.595 (Processing) + 29.394 (Spinning, Weaving and Dyeing) = 96.94", 393 | value: 96.94, 394 | unit: "kgCO2eq/kg", 395 | countryIds: ["china"], 396 | version: ModelVersion.version_0_4_0, 397 | }, 398 | { 399 | id: "emissionFactor/material/nylon/standard", 400 | label: "Nylon", 401 | source: 402 | "Value for 'Synthetic Fibers - Nylon - Conventional' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?q=nylon+-+conventional&refine.raw_material=Synthetic+Fibers+&refine.environmental_impact_group=GHGs&refine.impact_country=Portugal) for Portugal: 0.951 (Extraction) + 18.070 (Processing) + 9.123 (Spinning, Weaving and Dyeing) = 28.144", 403 | value: 28.144, 404 | unit: "kgCO2eq/kg", 405 | countryIds: ["portugal"], 406 | version: ModelVersion.version_0_4_0, 407 | }, 408 | { 409 | id: "emissionFactor/material/nylon/standard", 410 | label: "Nylon", 411 | source: 412 | "Value for 'Synthetic Fibers - Nylon - Conventional' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=Nylon+-+Conventional&refine.impact_country=Vietnam): 0.951 (Extraction) + 55.795 (Processing) + 29.394 (Spinning, Weaving and Dyeing, for China) = 86.14", 413 | value: 86.14, 414 | unit: "kgCO2eq/kg", 415 | countryIds: ["vietnam"], 416 | version: ModelVersion.version_0_5_0, 417 | comments: "Using China's value for 'Spinning Weaving and Dyeing' step.", 418 | }, 419 | { 420 | id: "emissionFactor/material/polyurethane/standard", 421 | label: "Polyurethane", 422 | source: 423 | "Using value for 'Plastic - Polyurethane' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyurethane). 10.374 (Processing).", 424 | value: 10.374, 425 | unit: "kgCO2eq/kg", 426 | countryIds: ["china", "vietnam"], 427 | version: ModelVersion.version_0_5_0, 428 | comments: 429 | "Using China's value for other asian countries: Vietnam. This value may lack other steps which are not present in the Kering's database (e.g. extraction, spinning/weaving/dyeing.", 430 | }, 431 | { 432 | id: "emissionFactor/material/polyurethane/standard", 433 | label: "Polyurethane", 434 | source: 435 | "Using 'plastic - polyurethane' value from [Kering Group's database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyurethan) (no value for Greece, using value for most european countries)", 436 | value: 2.785, 437 | unit: "kgCO2eq/kg", 438 | countryIds: ["greece"], 439 | version: ModelVersion.version_0_3_0, 440 | comments: 441 | "There's only a 'processing' step in Kering's database. It may lack the 'extraction' and 'spinning/weaving/dyeing' steps.", 442 | }, 443 | { 444 | id: "emissionFactor/material/polyurethane/standard", 445 | label: "Polyurethane", 446 | source: 447 | "Using 'plastic - polyurethane' value from [Kering Group's database](chttps://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyurethan&refine.impact_country=Spain)", 448 | value: 2.785, 449 | unit: "kgCO2eq/kg", 450 | countryIds: ["spain"], 451 | version: ModelVersion.version_0_3_0, 452 | comments: 453 | "There's only a 'processing' step in Kering's database. It may lack the 'extraction' and 'spinning/weaving/dyeing' steps.", 454 | }, 455 | { 456 | id: "emissionFactor/material/polyurethane/standard", 457 | label: "Polyurethane", 458 | source: 459 | "Using 'plastic - polyurethane' value from [Kering Group's database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyurethan) (no value for Portugal, using value for most european countries)", 460 | value: 2.785, 461 | unit: "kgCO2eq/kg", 462 | countryIds: ["portugal"], 463 | version: ModelVersion.version_0_3_0, 464 | comments: 465 | "There's only a 'processing' step in Kering's database. It may lack the 'extraction' and 'spinning/weaving/dyeing' steps.", 466 | }, 467 | { 468 | id: "emissionFactor/material/polyurethane/recycled", 469 | label: "Polyurethane - Recycled", 470 | source: 471 | "Using 'plastic - polyurethane' value from [Kering Group's database](chttps://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyurethan&refine.impact_country=Spain). Not specific to recycled material.", 472 | value: 2.785, 473 | unit: "kgCO2eq/kg", 474 | countryIds: ["spain"], 475 | version: ModelVersion.version_0_3_0, 476 | comments: 477 | "No specific value for recycled, this needs to be improved. There's only a 'processing' step in Kering's database. It may lack the 'extraction' and 'spinning/weaving/dyeing' steps.", 478 | }, 479 | { 480 | id: "emissionFactor/material/polyurethane/recycled", 481 | label: "Polyurethane - Recycled", 482 | source: 483 | "Using 'plastic - polyurethane' value from [Kering Group's database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=polyurethan) (no value for Portugal, using value for most european countries). Not specified to recycled material.", 484 | value: 2.785, 485 | unit: "kgCO2eq/kg", 486 | countryIds: ["portugal"], 487 | version: ModelVersion.version_0_3_0, 488 | comments: 489 | "No specific value for recycled, this needs to be improved. There's only a 'processing' step in Kering's database. It may lack the 'extraction' and 'spinning/weaving/dyeing' steps.", 490 | }, 491 | { 492 | id: "emissionFactor/material/polyethylene/recycled", 493 | label: "PE - Recycled", 494 | source: 495 | "Kering database, Raw Material Intensities 2020: https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/", 496 | value: 1.48, 497 | unit: "kgCO2eq/kg", 498 | countryIds: ["china"], 499 | version: ModelVersion.version_0_2_0, 500 | }, 501 | { 502 | id: "emissionFactor/material/polyethylene/recycled", 503 | label: "PE - Recycled", 504 | source: 505 | "Kering database, Raw Material Intensities 2020: https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/", 506 | value: 0.397, 507 | unit: "kgCO2eq/kg", 508 | countryIds: ["italy"], 509 | version: ModelVersion.version_0_2_0, 510 | }, 511 | { 512 | id: "emissionFactor/material/polyethylene/recycled", 513 | label: "PE - Recycled", 514 | source: "Using value for Italy", 515 | value: 0.397, 516 | unit: "kgCO2eq/kg", 517 | countryIds: ["portugal"], 518 | version: ModelVersion.version_0_2_0, 519 | }, 520 | { 521 | id: "emissionFactor/material/eva/standard", 522 | label: "EVA - Standard", 523 | source: 524 | "Values from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=eva) for 'Plastic - eva' and China: 0.226 (Extraction) + 7.563 (Processing): 7.789", 525 | value: 7.789, 526 | unit: "kgCO2eq/kg", 527 | countryIds: ["china", "vietnam"], 528 | version: ModelVersion.version_0_5_0, 529 | comments: "Using value for China for other asian countries: Vietnam.", 530 | }, 531 | { 532 | id: "emissionFactor/material/eva/standard", 533 | label: "EVA - Standard", 534 | source: 535 | "Values from [Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=eva) for 'Plastic - eva' and Italy or France: 0.112 (Extraction) + 2.126 (Processing): 2.238", 536 | value: 2.238, 537 | unit: "kgCO2eq/kg", 538 | countryIds: ["italy", "portugal", "france"], 539 | version: ModelVersion.version_0_5_0, 540 | comments: 541 | "Using value for Italy, France for other european countries without emission factor: Portugal, France.", 542 | }, 543 | { 544 | id: "emissionFactor/material/eva/recycled", 545 | label: "EVA - Recycled", 546 | source: "Using our value for 'eva/standard'.", 547 | value: 2.238, 548 | unit: "kgCO2eq/kg", 549 | countryIds: ["italy", "portugal", "france"], 550 | version: ModelVersion.version_0_5_0, 551 | comments: 552 | "Not using a specific value for recycled EVA. Using Italy's value for several european countries: Portugal, France.", 553 | }, 554 | { 555 | id: "emissionFactor/material/eva/recycled", 556 | label: "EVA - Recycled", 557 | source: "Using our value for 'eva/standard'.", 558 | value: 7.789, 559 | unit: "kgCO2eq/kg", 560 | countryIds: ["china", "vietnam"], 561 | version: ModelVersion.version_0_5_0, 562 | comments: 563 | "Using China's value for other asian countries: Vietnam. Not using a specific value for recycled EVA.", 564 | }, 565 | { 566 | id: "emissionFactor/material/leather/cattle", 567 | label: "Leather - Cattle", 568 | source: 569 | "Using 'Leather - beef - calf - conventional' value from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.raw_material=Leather+-+beef+&refine.impact_country=China&refine.environmental_impact_group=GHGs&q=conventional) for China: 20.917 (Animal rearing) + 0.569 (Abattoir) + 0.866 (Tanning - RH_WB) + 1.054 (Tanning - WB_FL) = 23.406", 570 | value: 23.406, 571 | unit: "kgCO2eq/kg", 572 | countryIds: ["china", "vietnam"], 573 | version: ModelVersion.version_0_5_0, 574 | comments: 575 | "Using the value for China for other countries in Asia (Vietnam).", 576 | }, 577 | { 578 | id: "emissionFactor/material/leather/cattle", 579 | label: "Leather - Cattle", 580 | source: 581 | "Emission factors for Leather/Beef/Italy from [Kering database, Raw Material Intensities 2020](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/): 16.883 (Rearing) + 0.513 (Slaughter) + 0.866 (Tanning RH_WB)", 582 | value: 18.262, 583 | unit: "kgCO2eq/kg", 584 | countryIds: ["italy", "portugal"], 585 | version: ModelVersion.version_0_5_0, 586 | comments: 587 | "Value based on Italy's emission factors. Used for other european countries (Portual).", 588 | }, 589 | { 590 | id: "emissionFactor/material/leather/cattle", 591 | label: "Leather - Cattle", 592 | source: 593 | "[Kering group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=leather+-+beef) value for 'Leather - beef - calf - conventional'. 'Animal rearing': 48.1535 (average for Argentina (48.110), Brazil (48.206), Chile (48.133), Paraguay (48.165) + 'Abattoir': 0.513 (same values for the 4 countries) + 'Tanning - RH_WB': 0.8635 (0.866 - Argentina, 0.861 - Brazil, 0.866 Chile, 0.861 Paraguay) + 'Tanning WB_FL': 1.054 (almost unique value for all countries available in the database, though there are no countries from South America). Total: 50.584.", 594 | value: 50.584, 595 | unit: "kgCO2eq/kg", 596 | countryIds: ["peru"], 597 | version: ModelVersion.version_0_5_0, 598 | comments: 599 | "Creating a value by averaging values from South America countries and using a default value for Tanning WB_FL step.", 600 | }, 601 | { 602 | id: "emissionFactor/material/leather/cattle", 603 | label: "Leather - Cattle", 604 | source: 605 | "Using 'Leather - beef - calf - conventional' value from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=Leather+-+beef+-+calf+-+conventional&refine.impact_country=Spain) for Spain: 16.984 (Animal rearing) + 0.513 (Abattoir) + 0.866 (Tanning - RH_WB) + 1.054 (Tanning - WB_FL) = 23.406", 606 | value: 19.417, 607 | unit: "kgCO2eq/kg", 608 | countryIds: ["spain"], 609 | version: ModelVersion.version_0_5_0, 610 | }, 611 | { 612 | id: "emissionFactor/material/leather/cattle/recycled", 613 | label: "Leather - Cattle, recycled", 614 | source: 615 | "None at this time. For now, we arbitrarily take 20% of the emission factor for cattle leather from China (23.406 * 0.2 = 4.6812)", 616 | value: 4.6812, 617 | unit: "kgCO2eq/kg", 618 | countryIds: ["china", "vietnam"], 619 | version: ModelVersion.version_0_5_0, 620 | comments: 621 | "Need to look for research material and sources to get a better emission factor. Using the value for China for other asian countries (Vietnam).", 622 | }, 623 | { 624 | id: "emissionFactor/material/leather/cattle/recycled", 625 | label: "Leather - Cattle, recycled", 626 | source: 627 | "None at this time. For now, we arbitrarily take 20% of the emission factor for cattle leather from Italy (18.262 * 0.2 = 3.6524)", 628 | value: 3.6524, 629 | unit: "kgCO2eq/kg", 630 | countryIds: ["italy", "portugal"], 631 | version: ModelVersion.version_0_5_0, 632 | comments: 633 | "Need to look for research material and sources to get a better emission factor. Using the value for Italy for other european countries (Portugal).", 634 | }, 635 | { 636 | id: "emissionFactor/material/leather/vegan/grape", 637 | label: "Leather - Vegan - Grape", 638 | description: 639 | "Alternative to leather, mostly made out of grape vegetal, mixed with other materials (generally polyester, polyurethan and cotton)", 640 | source: 641 | "Sami.eco LCA report for MoEA shoes. Based on 1.12 kgCO2eq estimation per pair of shoes and 0.184 kg approximate estimate for the upper's weight. 1.12/0.184 = 6.087 kgCO2eq/kg. The manufacturer of the material is not indicated (shoes produced in Portugal).", 642 | value: 6.087, 643 | unit: "kgCO2eq/kg", 644 | countryIds: ["portugal", "italy"], 645 | version: ModelVersion.version_0_5_0, 646 | comments: "We don't know the country origin for this material.", 647 | }, 648 | { 649 | id: "emissionFactor/material/leather/vegan/cactus", 650 | label: "Leather - Vegan - Cactus", 651 | description: 652 | "Alternative to leather, mostly made out of cactus vegetal, mixed with other materials (generally polyester, polyurethan and cotton)", 653 | source: 654 | "Sami.eco LCA report for MoEA shoes. Based on 1.02 kgCO2eq estimation per pair of shoes and 0.160 kg approximate estimate for the upper's weight. 1.02/0.160 = 6.375 kgCO2eq/kg. The manufacturer of the material is not indicated (shoes produced in Portugal).", 655 | value: 6.375, 656 | unit: "kgCO2eq/kg", 657 | countryIds: ["portugal"], 658 | version: ModelVersion.version_0_5_0, 659 | comments: "We don't know the country origin for this material.", 660 | }, 661 | { 662 | id: "emissionFactor/material/leather/vegan/corn", 663 | label: "Leather - Vegan - Corn", 664 | description: 665 | "Alternative to leather, mostly made out of corn vegetal, mixed with other materials (generally polyester, polyurethan and cotton)", 666 | source: 667 | "Sami.eco LCA report for MoEA shoes. Based on 2.13 kgCO2eq estimation per pair of shoes and 0.560 kg approximate estimate for the upper's weight. 2.13/0.560 = 3.804 kgCO2eq/kg. The manufacturer of the material is not indicated (shoes produced in Portugal).", 668 | value: 3.804, 669 | unit: "kgCO2eq/kg", 670 | countryIds: ["portugal", "spain", "italy"], 671 | version: ModelVersion.version_0_5_0, 672 | comments: 673 | "We don't know the country origin for this material. This emission factor may embed emission for end-of-life (recycling or incineration), which should not be the case for our current model. Using the same values for different european countries.", 674 | }, 675 | { 676 | id: "emissionFactor/material/leather/vegan/pineapple", 677 | label: "Leather - Vegan - Pineapple", 678 | description: 679 | "Alternative to leather, mostly made out of pineapple vegetal, mixed with other materials (generally polyester, polyurethan and cotton)", 680 | source: 681 | "Sami.eco LCA report for MoEA shoes. Based on 1.72 kgCO2eq estimation per pair of shoes and 0.263 kg approximate estimate for the upper's weight. 1.72/0.263 = 6.540 kgCO2eq/kg. The manufacturer of the material is not indicated (shoes produced in Portugal).", 682 | value: 6.54, 683 | unit: "kgCO2eq/kg", 684 | countryIds: ["portugal"], 685 | version: ModelVersion.version_0_5_0, 686 | comments: 687 | "We don't know the country origin for this material. This emission factor may embed emission for end-of-life (recycling or incineration), which should not be the case for our current model.", 688 | }, 689 | { 690 | id: "emissionFactor/material/leather/vegan/apple", 691 | label: "Leather - Vegan - Apple", 692 | description: 693 | "Alternative to leather, mostly made out of apple vegetal, mixed with other materials (generally polyester, polyurethan and cotton)", 694 | source: 695 | "Sami.eco LCA report for MoEA shoes. Based on 2.23 kgCO2eq estimation per pair of shoes and 0.160 kg approximate estimate for the upper's weight. 2.23/0.160 = 13.937 kgCO2eq/kg. The manufacturer of the material is not indicated (shoes produced in Portugal).", 696 | value: 13.937, 697 | unit: "kgCO2eq/kg", 698 | countryIds: ["portugal", "spain"], 699 | version: ModelVersion.version_0_5_0, 700 | comments: 701 | "We don't know the country origin for this material. This emission factor may embed emission for end-of-life (recycling or incineration), which should not be the case for our current model.", 702 | }, 703 | { 704 | id: "emissionFactor/material/leather/vegan/undetermined", 705 | label: "Leather - Vegan", 706 | description: 707 | "Alternative to leather, mostly made out of vegetal, mixed with other materials (generally polyester, polyurethan and cotton)", 708 | source: 709 | "Internal value calculated by average our current values for 'Leather - Vegan' (cactus, corn, pineapple, apple, grape)", 710 | value: 7.349, 711 | unit: "kgCO2eq/kg", 712 | countryIds: ["portugal"], 713 | version: ModelVersion.version_0_5_0, 714 | comments: 715 | "We don't know the country origin for this material. This emission factor may embed emission for end-of-life (recycling or incineration), which should not be the case for our current model.", 716 | }, 717 | { 718 | id: "emissionFactor/material/leather/cattle/vegetan", 719 | label: "Leather - Cattle - Vegetan", 720 | source: 721 | "Emission factors for 'Leather - beef - calf - conventional' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=Leather+-+beef+-+calf+-+conventional&refine.impact_country=Italy): 16.883 (Animal rearing) + 0.513 (Slaughter) + 0.637 (Tanning RH_WB Metal-free) + 0.954 (Tanning WB_FL Metal-free): 18.987", 722 | value: 18.987, 723 | unit: "kgCO2eq/kg", 724 | countryIds: ["italy", "portugal"], 725 | version: ModelVersion.version_0_5_0, 726 | comments: 727 | "Using the value for Italy for other european countries: Portugal.", 728 | }, 729 | { 730 | id: "emissionFactor/material/leather/cattle/vegetan", 731 | label: "Leather - Cattle - Vegetan", 732 | source: 733 | '[Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?q=leather+-+beef+-+conventional&refine.environmental_impact_group=GHGs&refine.impact_country=China), values for "Leather - beef - calf - conventional":\n- Animal rearing: 20.917\n- Abattoir: 0.569\n- Tanning RH_WB Metal-free: 0.866\nTotal: 22.352 kgCO2eq/kg.', 734 | value: 22.352, 735 | unit: "kgCO2eq/kg", 736 | countryIds: ["china", "vietnam"], 737 | version: ModelVersion.version_0_5_0, 738 | comments: "Using the value for Italy for other asian countries: Vietnam.", 739 | }, 740 | { 741 | id: "emissionFactor/material/leather/synthetic", 742 | label: "Leather - Synthetic", 743 | source: 744 | "Using value for 'microfiber/undetermined' as a proxy (microfiber is used to produce artifical suede)", 745 | value: 29.329, 746 | unit: "kgCO2eq/kg", 747 | countryIds: ["china"], 748 | version: ModelVersion.version_0_5_0, 749 | comments: "Fallback to 'microfiber/undetermined'", 750 | }, 751 | { 752 | id: "emissionFactor/material/microsuede/undetermined", 753 | label: "Microsuede", 754 | source: 755 | "Microsuede is a type of microfiber ([source](https://www.beanbagsrus.com.au/blog/difference-between-microsuede-microfiber/)). Without more details on the type of microfiber, we use the value for 'microfiber/undetermined'.", 756 | value: 29.329, 757 | unit: "kgCO2eq/kg", 758 | countryIds: ["china"], 759 | version: ModelVersion.version_0_5_0, 760 | comments: "Fallback on 'microfiber/undetermined'.", 761 | }, 762 | { 763 | id: "emissionFactor/material/microsuede/undetermined", 764 | label: "Microsuede", 765 | source: 766 | "Microsuede is a type of microfiber ([source](https://www.beanbagsrus.com.au/blog/difference-between-microsuede-microfiber/)). Without more details on the type of microfiber, we use the value for 'microfiber/undetermined'.", 767 | value: 8.543, 768 | unit: "kgCO2eq/kg", 769 | countryIds: ["greece"], 770 | version: ModelVersion.version_0_5_0, 771 | comments: "Fallback on 'microfiber/undetermined'.", 772 | }, 773 | { 774 | id: "emissionFactor/material/microsuede/undetermined", 775 | label: "Microsuede", 776 | source: 777 | "Microsuede is a type of microfiber ([source](https://www.beanbagsrus.com.au/blog/difference-between-microsuede-microfiber/)). Without more details on the type of microfiber, we use the value for 'microfiber/undetermined'.", 778 | value: 8.635, 779 | unit: "kgCO2eq/kg", 780 | countryIds: ["italy"], 781 | version: ModelVersion.version_0_5_0, 782 | comments: "Fallback on 'microfiber/undetermined'.", 783 | }, 784 | { 785 | id: "emissionFactor/material/microsuede/cotton+polyester", 786 | label: "Microsuede - Cotton and polyester", 787 | source: 788 | "Using a weighted mean of emission factors:\n- 50% 'cotton/standard': 6.945 (source: our value for 'cotton/standard' from Portugal)\n- 50% 'polyester/standard': 8.558 (source: our value for 'polyester/standard' from Portugal)", 789 | value: 7.7515, 790 | unit: "kgCO2eq/kg", 791 | countryIds: ["portugal"], 792 | version: ModelVersion.version_0_3_0, 793 | comments: 794 | "Constructed from 'cotton/standard - Portugal' and 'polyester/standard - Portugal' emission factors.", 795 | }, 796 | { 797 | id: "emissionFactor/material/microfiber/undetermined", 798 | label: "Microfiber", 799 | source: 800 | "Without details about the nature of the microfiber, we assume it's mostly made of polyester ([source Wikipedia](https://en.wikipedia.org/wiki/Microfiber)). Using our 'polyester/standard' value.", 801 | value: 8.558, 802 | unit: "kgCO2eq/kg", 803 | countryIds: ["portugal"], 804 | version: ModelVersion.version_0_5_0, 805 | comments: "Fallback to 'polyester/standard' for Portugal.", 806 | }, 807 | { 808 | id: "emissionFactor/material/microfiber/undetermined", 809 | label: "Microfiber", 810 | source: 811 | "Without details about the nature of the microfiber, we assume it's mostly made of polyester ([source Wikipedia](https://en.wikipedia.org/wiki/Microfiber)). Using our 'polyester/standard' value.", 812 | value: 8.49775, 813 | unit: "kgCO2eq/kg", 814 | countryIds: ["greece"], 815 | version: ModelVersion.version_0_5_0, 816 | comments: "Fallback to 'polyester/standard' for Greece.", 817 | }, 818 | { 819 | id: "emissionFactor/material/wood/cork", 820 | label: "Cork", 821 | source: 822 | "Using 'Other - cork' emission factors from [Kering Group database](https://kering-group.opendatasoft.com):\n- Production: 0.006 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cork&refine.processstep=Production&refine.impact_country=Portugal))\n- Processing: 0.127 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cork&refine.impact_country=Portugal&refine.processstep=Processing))", 823 | value: 0.133, 824 | unit: "kgCO2eq/kg", 825 | countryIds: ["portugal"], 826 | version: ModelVersion.version_0_3_0, 827 | }, 828 | { 829 | id: "emissionFactor/material/wood/cork/recycled", 830 | label: "Cork - Recycled", 831 | source: 832 | "Using 'Other - cork' emission factors from [Kering Group database](https://kering-group.opendatasoft.com):\n- Production: 0.006 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cork&refine.impact_country=Spain&refine.processstep=Production))\n- Processing: 0.127 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cork&refine.impact_country=Spain&refine.processstep=Processing))", 833 | value: 0.133, 834 | unit: "kgCO2eq/kg", 835 | countryIds: ["spain"], 836 | version: ModelVersion.version_0_3_0, 837 | comments: 838 | "Not taking into account that it is recycled in the absence of existing emission factors for it.", 839 | }, 840 | { 841 | id: "emissionFactor/material/wool/undetermined", 842 | label: "Wool", 843 | source: 844 | "Using value for 'Wool - conventional' for China from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?q=wool&refine.environmental_impact_group=GHGs&refine.impact_country=China) (no specific value for Vietnam: 127.454 (Animal rearing) + 1.542 (Washing) + 14.027 (Spinning, weaving and dyeing) = 143.023", 845 | value: 143.023, 846 | unit: "kgCO2eq/kg", 847 | countryIds: ["china", "vietnam"], 848 | version: ModelVersion.version_0_5_0, 849 | comments: 850 | "Fallback to 'wool/conventional'. Using China's value for other asian countries: Vietnam.", 851 | }, 852 | { 853 | id: "emissionFactor/material/paper/cardboard", 854 | label: "Cardboard", 855 | source: 856 | "Using 'paper and cardboard - uncertified' values from [Kering Group database](https://kering-group.opendatasoft.com):\n- Production (no value for Portugal, using value for Italy, Spain, Belgium...): 0.779 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cardboard&refine.processstep=Production))\n- Processing (no value for Portugal, using value for Spain): 0.511 ([source](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=cardboard&refine.processstep=Processing))", 857 | value: 1.29, 858 | unit: "kgCO2eq/kg", 859 | countryIds: ["portugal"], 860 | version: ModelVersion.version_0_3_0, 861 | comments: 862 | "This could be improved by searching for a better extrapolation for the processing value than using Spain's", 863 | }, 864 | { 865 | id: "emissionFactor/material/seaqual", 866 | label: "Seaqual", 867 | source: 868 | "Internally constructed. According to Seads ([source](https://seads.global/pages/espadrilles-made-of-ocean-plastic-materials)), the carbon emission of Seaqual's production is reduced by 60% when compared to a polyester yarn. Using 0.4 * our value for 'polyester - conventional' from Portugal (8.558)", 869 | value: 3.4232, 870 | unit: "kgCO2eq/kg", 871 | countryIds: ["portugal"], 872 | version: ModelVersion.version_0_3_0, 873 | comments: 874 | "The following claims on carbon footprint reduction of Seaqual by Seads ([source](https://seads.global/pages/espadrilles-made-of-ocean-plastic-materials)) must be verified: 'Seaqual® Yarn is a unique 100% recycled yarn made from 10% Upcycled Marine Plastic, recovered from our oceans, beaches and rivers, and 90% recycled PET packaging waste from households. It’s almost identical in look and feel to virgin polyester yarn, but reduces water waste by 40%, energy consumption by 50%, and carbon emissions by 60% during the production process. Seaqual Yarn® is fully recyclable. ) must be verified: 'Seaqual® Yarn is a unique 100% recycled yarn made from 10% Upcycled Marine Plastic, recovered from our oceans, beaches and rivers, and 90% recycled PET packaging waste from households. It’s almost identical in look and feel to virgin polyester yarn, but reduces water waste by 40%, energy consumption by 50%, and carbon emissions by 60% during the production process. Seaqual Yarn® is fully recyclable.'", 875 | }, 876 | { 877 | id: "emissionFactor/material/seaqual", 878 | label: "Seaqual", 879 | source: 880 | "Internally constructed. According to Seads ([source](https://seads.global/pages/espadrilles-made-of-ocean-plastic-materials)), the carbon emission of Seaqual's production is reduced by 60% when compared to a polyester yarn. Using 0.4 * our value for 'polyester - conventional' from Spain (8.543)", 881 | value: 3.4172, 882 | unit: "kgCO2eq/kg", 883 | countryIds: ["spain"], 884 | version: ModelVersion.version_0_3_0, 885 | comments: 886 | "The following claims on carbon footprint reduction of Seaqual by Seads ([source](https://seads.global/pages/espadrilles-made-of-ocean-plastic-materials)) must be verified: 'Seaqual® Yarn is a unique 100% recycled yarn made from 10% Upcycled Marine Plastic, recovered from our oceans, beaches and rivers, and 90% recycled PET packaging waste from households. It’s almost identical in look and feel to virgin polyester yarn, but reduces water waste by 40%, energy consumption by 50%, and carbon emissions by 60% during the production process. Seaqual Yarn® is fully recyclable. ) must be verified: 'Seaqual® Yarn is a unique 100% recycled yarn made from 10% Upcycled Marine Plastic, recovered from our oceans, beaches and rivers, and 90% recycled PET packaging waste from households. It’s almost identical in look and feel to virgin polyester yarn, but reduces water waste by 40%, energy consumption by 50%, and carbon emissions by 60% during the production process. Seaqual Yarn® is fully recyclable.'", 887 | }, 888 | { 889 | id: "emissionFactor/material/special/bloom/foam", 890 | label: "Bloom Foam", 891 | source: 892 | 'Internally constructed. According to Bloom ([source](https://www.bloommaterials.com/rise/)), using Bloom Foam sequesters carbon dioxide from the atmosphere through the algae used to produce the material. Converting this to an emission factor is out-of-scope for now so we will assume this may be considered as a "net-zero" material, and we will use a 0 value.', 893 | value: 0, 894 | unit: "kgCO2eq/kg", 895 | countryIds: ["italy", "vietnam"], 896 | version: ModelVersion.version_0_5_0, 897 | comments: 898 | "This material must be investigated to determine a better emission factor. Same value used for different countries.", 899 | }, 900 | { 901 | id: "emissionFactor/material/fabric/hemp", 902 | label: "Fabric - Hemp", 903 | source: 904 | "Value for 'Plant Fiber - Hemp' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=hemp): 1.773 (Crop Farming, value for several European countries in absence of value for Portugal) + 0 (Ginning) + 4.280 (Spinning, Weaving and Dyeing for Portugal)", 905 | value: 6.053, 906 | unit: "kgCO2eq/kg", 907 | countryIds: ["portugal"], 908 | version: ModelVersion.version_0_4_0, 909 | }, 910 | { 911 | id: "emissionFactor/material/fabric/hemp", 912 | label: "Fabric - Hemp", 913 | source: 914 | "Value for 'Plant Fiber - Hemp' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=hemp): 4.038 (Crop farming) + 0.000 (Ginning) + 13.246 (Spinning Weaving and Dyeing) = 17.284", 915 | value: 17.284, 916 | unit: "kgCO2eq/kg", 917 | countryIds: ["china", "senegal"], 918 | version: ModelVersion.version_0_5_0, 919 | comments: "Using China's value for other countries: Senegal.", 920 | }, 921 | { 922 | id: "emissionFactor/material/fabric/jute", 923 | label: "Fabric - Jute", 924 | source: 925 | "No known value for 'Jute', using value for 'Plant Fiber - Hemp' from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=hemp): 1.773 (Crop Farming, value for several European countries in absence of value for Portugal) + 0 (Ginning) + 4.280 (Spinning, Weaving and Dyeing for Portugal)", 926 | value: 6.053, 927 | unit: "kgCO2eq/kg", 928 | countryIds: ["portugal"], 929 | version: ModelVersion.version_0_4_0, 930 | comments: "Fallback on 'fabric/hemp'", 931 | }, 932 | { 933 | id: "emissionFactor/material/fabric/jute", 934 | label: "Fabric - Jute", 935 | source: 936 | "No known value for 'Jute', using our 'fabric/hemp' value for China.", 937 | value: 17.284, 938 | unit: "kgCO2eq/kg", 939 | countryIds: ["china", "senegal"], 940 | version: ModelVersion.version_0_5_0, 941 | comments: 942 | "Fallback on 'fabric/hemp'. Using China's value for other countries: Senegal.", 943 | }, 944 | { 945 | id: "emissionFactor/material/vegetal/waste", 946 | label: "Vegetal - Waste", 947 | source: "Internal hypothesis", 948 | value: 0.534, 949 | unit: "kgCO2eq/kg", 950 | countryIds: ["italy", "portugal"], 951 | version: ModelVersion.version_0_5_0, 952 | comments: 953 | "With no information on the average emission factor for vegetal waste used in fabrics, we currently use the same value as for natural rubber, since it's currently mostly used in the products we saw to complete rubber in outsoles. This must be improved to take into account part of the farming impact, as well as the processing impact.", 954 | }, 955 | { 956 | id: "emissionFactor/material/tencel", 957 | label: "Tencel", 958 | source: 959 | "[IFATCC report on Tencel environmental footprint](http://www.ifatcc.org/wp-content/uploads/2018/01/A03-Taylor.pdf)", 960 | value: 2.08, 961 | unit: "kgCO2eq/kg", 962 | countryIds: ["italy", "china"], 963 | version: ModelVersion.version_0_5_0, 964 | comments: "Same value used for different countries.", 965 | }, 966 | { 967 | id: "emissionFactor/material/plastic/pet/standard", 968 | label: "Plastic - PET - Standard", 969 | value: 3.26, 970 | unit: "kgCO2eq/kg", 971 | countryIds: ["france", "italy", "spain"] as string[], // europe 972 | version: ModelVersion.version_0_5_0, 973 | source: 974 | "Value for 'Primary or pure amorphous PET' from [ADEME Bilan GES database](https://www.bilans-ges.ademe.fr/documentation/UPLOAD_DOC_EN/index.htm?produits_en_caoutchouc_et_en_p.htm), from PlasticsEurope 2005.", 975 | comments: 976 | "Value seems to be applicable Europe. This is raw plastic and should only be used for molded components (like soles).", 977 | }, 978 | { 979 | id: "emissionFactor/material/plastic/pet/recycled", 980 | label: "Plastic - PET - Recycled", 981 | value: 0.202, 982 | unit: "kgCO2eq/kg", 983 | countryIds: ["france", "italy", "spain", "portugal"] as string[], // europe 984 | version: ModelVersion.version_0_5_0, 985 | source: 986 | "Value for 'PET (recycled)' from [ADEME Bilan GES database](https://www.bilans-ges.ademe.fr/documentation/UPLOAD_DOC_EN/index.htm?produits_en_caoutchouc_et_en_p.htm). Assumes mechanical recycling.", 987 | comments: "Value seems to be applicable for Europe.", 988 | }, 989 | { 990 | id: "emissionFactor/material/plastic/pet/standard", 991 | label: "Plastic - PET - Standard", 992 | value: 11.41, 993 | unit: "kgCO2eq/kg", 994 | countryIds: ["china", "vietnam"] as string[], 995 | version: ModelVersion.version_0_5_0, 996 | source: 997 | "In the absence of a value for China, we estimate the PET emission factor by applying the factor observed between the emission factor for Polyethylene between Europe and China (using 'plastic - pe' values from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=&refine.raw_material=Plastic+-+pe&refine.impact_country=China)). PE's emission factor for Europe is: 0.203 (Extraction) + 3.856 (Processing) = 4.059. Value for China is: 0.203 (Extraction) + 14.009 (Processing) = 14.212. Factor is: x3.5. Taking Europe's 'pet/standard' value, we have for China: 3.5 * 3.26 = 11.41.", 998 | comments: 999 | "Internal estimation based on Europe's value and Value seems to be applicable Europe. This is raw plastic and should only be used for molded components (like soles).", 1000 | }, 1001 | { 1002 | id: "emissionFactor/material/plastic/pet/recycled", 1003 | label: "Plastic - PET - Recycled", 1004 | value: 0.66, 1005 | unit: "kgCO2eq/kg", 1006 | countryIds: ["china", "vietnam"] as string[], 1007 | version: ModelVersion.version_0_5_0, 1008 | source: 1009 | "In the absence of a value for China, we estimate the PET emission factor by applying the factor observed between the emission factor for Polyethylene between Europe and China (using 'plastic - pe' values from [Kering Group database](https://kering-group.opendatasoft.com/explore/dataset/raw-material-intensities-2020/table/?refine.environmental_impact_group=GHGs&q=&refine.raw_material=Plastic+-+pe&refine.impact_country=China)). PE's emission factor for Europe is: 0.203 (Extraction) + 3.856 (Processing) = 4.059. Value for China is: 0.203 (Extraction) + 14.009 (Processing) = 14.212. Factor is: x3.5. Taking Europe's 'pet/recycled' value, we have for China: 0.202 * 3.26 = 0.66.", 1010 | comments: "Value seems to be applicable for Europe.", 1011 | }, 1012 | { 1013 | id: "emissionFactor/material/missingMaterialPart", 1014 | label: "Missing material part - Emission factor for shoes", 1015 | source: "Carbonfact", 1016 | value: 5.92, 1017 | unit: "kgCO2eq/kg", 1018 | countryIds: ["world"], 1019 | comments: 1020 | "This value is used to represent the emissions of unknown materials in our shoes model. It has been defined using the mean of the aggregate emission per kg of final product for 2 generic shoes LCA in ADEME BaseImpact database: leather shoes and cotton shoes.\n\nThe materials for the leather shoes represent a total of 3.06 kgCO2eq (NB: ADEME's model doesn't include the cattle stock raising in the material's emissions) and weight 0.8 kg. The emission factor is thus 3.06 / 0.8 = 3.82 kgCO2eq/kg. For the cotton shoes, the emission factor is 4.81 / 0.6 = 8.02 kgCO2eq/kg. The result is then (3.82 + 8.02) / 2 = 5.92.", 1021 | version: ModelVersion.version_0_1_0, 1022 | }, 1023 | { 1024 | id: "emissionFactor/energy/electricity", 1025 | label: "Electricity", 1026 | source: "Unknown", 1027 | value: 0.6, 1028 | unit: "kgCO2eq/kWh", 1029 | countryIds: ["china"], 1030 | version: ModelVersion.version_0_1_0, 1031 | }, 1032 | { 1033 | id: "emissionFactor/energy/electricity", 1034 | label: "Electricity", 1035 | source: 1036 | "https://www.statista.com/statistics/1190081/carbon-intensity-outlook-of-australia/", 1037 | value: 0.656, 1038 | unit: "kgCO2eq/kWh", 1039 | countryIds: ["australia"], 1040 | version: ModelVersion.version_0_2_0, 1041 | }, 1042 | { 1043 | id: "emissionFactor/energy/electricity", 1044 | label: "Electricity", 1045 | source: 1046 | "https://www.eea.europa.eu/data-and-maps/indicators/overview-of-the-electricity-production-3/assessment-1", 1047 | value: 0.21, 1048 | unit: "kgCO2eq/kWh", 1049 | countryIds: ["spain"], 1050 | version: ModelVersion.version_0_2_0, 1051 | }, 1052 | { 1053 | id: "emissionFactor/energy/electricity", 1054 | label: "Electricity", 1055 | source: 1056 | "https://www.eea.europa.eu/data-and-maps/indicators/overview-of-the-electricity-production-3/assessment-1", 1057 | value: 0.233, 1058 | unit: "kgCO2eq/kWh", 1059 | countryIds: ["italy"], 1060 | version: ModelVersion.version_0_2_0, 1061 | }, 1062 | { 1063 | id: "emissionFactor/energy/electricity", 1064 | label: "Electricity", 1065 | source: 1066 | "https://www.eea.europa.eu/data-and-maps/indicators/overview-of-the-electricity-production-3/assessment-1", 1067 | value: 0.255, 1068 | unit: "kgCO2eq/kWh", 1069 | countryIds: ["portugal"], 1070 | version: ModelVersion.version_0_2_0, 1071 | }, 1072 | { 1073 | id: "emissionFactor/energy/electricity", 1074 | label: "Electricity", 1075 | source: 1076 | "World Bank, https://documents.worldbank.org/curated/en/781731523025593046/Implementation-Completion-and-Results-Report-ICR-Document-04032018.docx", 1077 | value: 0.8154, 1078 | unit: "kgCO2eq/kWh", 1079 | countryIds: ["vietnam"], 1080 | version: ModelVersion.version_0_3_0, 1081 | }, 1082 | { 1083 | id: "emissionFactor/energy/electricity", 1084 | label: "Electricity", 1085 | source: 1086 | "[climate-transparency.org China 2019 report](https://www.climate-transparency.org/wp-content/uploads/2019/11/B2G_2019_China.pdf), page 7. Original source: Enerdata 2019.", 1087 | value: 0.555, 1088 | unit: "kgCO2eq/kWh", 1089 | countryIds: ["china"], 1090 | version: ModelVersion.version_0_4_0, 1091 | }, 1092 | { 1093 | id: "emissionFactor/energy/electricity", 1094 | label: "Electricity", 1095 | source: 1096 | "Value for 2020 from [Statista](https://www.statista.com/statistics/1190067/carbon-intensity-outlook-of-france/)", 1097 | value: 0.057, 1098 | unit: "kgCO2eq/kWh", 1099 | countryIds: ["france"], 1100 | version: ModelVersion.version_0_4_1, 1101 | }, 1102 | { 1103 | id: "emissionFactor/energy/electricity", 1104 | label: "Electricity", 1105 | source: 1106 | "Using ourworldindata.org data on energy mix for Senegal in 2019 ([source](https://ourworldindata.org/grapher/share-elec-by-source?country=~SEN)): 88.51% oil, 7.78% hydropower, 2.01% other renewables (not solar, wind or hydropower), 1.70% solar. Using mean values for emission factors from [Comparison of Lifecycle GHG of Various Electricity Generation Sources from world-nuclear.org](https://www.world-nuclear.org/uploadedFiles/org/WNA/Publications/Working_Group_Reports/comparison_of_lifecycle.pdf) (unit is tCO2e/GWh): gas 499, wind 26, coal 888, oil 733, solar 85, hydropower 26, other renewables 45 (using biomass' value). Result is: 88.51% * 733 + 7.78% * 26 + 2.01% * 45 + 1.7% * 85 = 0.6531506 kgCO2eq/kWh.", 1107 | value: 0.6531506, 1108 | unit: "kgCO2eq/kWh", 1109 | countryIds: ["senegal"], 1110 | version: ModelVersion.version_0_4_1, 1111 | }, 1112 | { 1113 | id: "emissionFactor/energy/electricity", 1114 | label: "Electricity", 1115 | source: 1116 | "Using ourworldindata.org data on energy mix for Greece in 2019 ([source](https://ourworldindata.org/grapher/share-elec-by-source?country=~GRC)): 36.75% gas, 18.42% wind, 15.29% coal, 12.06% oil, 9.01% solar, 7.84% hydropower, 0.65% other renewables (not solar, wind or hydropower). Using mean values for emission factors from [Comparison of Lifecycle GHG of Various Electricity Generation Sources from world-nuclear.org](https://www.world-nuclear.org/uploadedFiles/org/WNA/Publications/Working_Group_Reports/comparison_of_lifecycle.pdf) (unit is tCO2e/GWh): gas 499, wind 26, coal 888, oil 733, solar 85, hydropower 26, other renewables 45 (using biomass' value). Result is: 36.75% * 499 + 18.42% * 26 + 15.29% * 888 + 12.06 * 733 + 9.01% * 85 + 7.84% * 26 + 0.65 * 45 = 0.4223361 kgCO2eq/kWh.", 1117 | value: 0.4223361, 1118 | unit: "kgCO2eq/kWh", 1119 | countryIds: ["greece"], 1120 | version: ModelVersion.version_0_4_1, 1121 | }, 1122 | { 1123 | id: "emissionFactor/energy/electricity", 1124 | label: "Electricity", 1125 | source: 1126 | "Using ourworldindata.org data on energy mix for Peru in 2019 ([source](https://ourworldindata.org/grapher/share-elec-by-source?country=~PER)): 58.60% hydropower, 34.09% gas, 3.49% wind, 1.51% solar, 1.19% coal, 1.12% other renewables (not solar, wind or hydropower). Using mean values for emission factors from [Comparison of Lifecycle GHG of Various Electricity Generation Sources from world-nuclear.org](https://www.world-nuclear.org/uploadedFiles/org/WNA/Publications/Working_Group_Reports/comparison_of_lifecycle.pdf) (unit is tCO2e/GWh): gas 499, wind 26, coal 888, oil 733, solar 85, hydropower 26, other renewables 45 (using biomass' value). Result is: 58.60% * 26 + 34.09% + 499 + 3.49% * 26 + 1.51% * 85 + 1.19% * 888 + 1.12% * 45 = 0.33829 kgCO2eq/kWh.", 1127 | value: 0.33829, 1128 | unit: "kgCO2eq/kWh", 1129 | countryIds: ["peru"], 1130 | version: ModelVersion.version_0_4_1, 1131 | }, 1132 | { 1133 | id: "fixedValue/lifeCycleAnalysisStep/manufacturing/shoes/energyConsumption/electricity", 1134 | label: "Electricity consumed for manufacturing a pair of shoes", 1135 | source: "ADEME BaseImpact", 1136 | value: 6.0, 1137 | unit: "kWh", 1138 | version: ModelVersion.version_0_5_0, 1139 | }, 1140 | { 1141 | id: "fixedValue/lifeCycleAnalysisStep/use/shoes", 1142 | label: "Emissions for the use of a pair of shoes", 1143 | source: 1144 | "Most references ignore the impact of using shoes or have a very low value", 1145 | value: 0, 1146 | unit: "kgCO2eq", 1147 | version: ModelVersion.version_0_5_0, 1148 | }, 1149 | { 1150 | id: `fixedValue/lifeCycleAnalysisStep/distribution/shoes/intercontinental/default`, 1151 | label: 1152 | 'Emissions for an average intercontinental distribution of a pair of shoes (from the manufacturing plant to the local distribution warehouse, a.k.a. "upstream freight")', 1153 | source: 1154 | "13 % * (12 000 km * ((0.7 kg shoes + 0.2 kg packaging) / 1 000 kg) t * 0.60 kgCO2eq/t.km) + 87 % * (18 000 km * ((0.7 kg shoes + 0.2 kg packaging) / 1 000) t * 0.015 kgCO2eq/t.km).\n- 13 % airplane, 87 % boat distribution from ADEME BaseImpact LCA for shoes.\n- Like in other parts of our methodology, we assume an average 0.7 kg weight for shoes and 0.2 kg for the packaging.\n- 12 000 km is an average trip length for intercontinental flights.\n- 18 000 km is an average trip length for intercontinental sea-freight routes.\n-Emission factors from [EASAC report](https://easac.eu/fileadmin/PDF_s/reports_statements/Decarbonisation_of_Tansport/EASAC_Decarbonisation_of_Transport_FINAL_March_2019.pdf).", 1155 | value: 1.05381, 1156 | unit: "kgCO2eq", 1157 | version: ModelVersion.version_0_5_0, 1158 | }, 1159 | { 1160 | id: `fixedValue/lifeCycleAnalysisStep/distribution/shoes/intercontinental/sea-only/conventional`, 1161 | label: 1162 | 'Emissions for 100% sea-freight intercontinental distribution of a pair of shoes (from the manufacturing plant to the local distribution warehouse, a.k.a. "upstream freight")', 1163 | source: 1164 | "(18 000 km * ((0.7 kg shoes + 0.2 kg packaging) / 1 000) t * 0.015 kgCO2eq/t.km).\n- Like in other parts of our methodology, we assume an average 0.7 kg weight for shoes and 0.2 kg for the packaging.\n- 18 000 km is an average trip length for intercontinental sea-freight routes.\n-Emission factors from [EASAC report](https://easac.eu/fileadmin/PDF_s/reports_statements/Decarbonisation_of_Tansport/EASAC_Decarbonisation_of_Transport_FINAL_March_2019.pdf).", 1165 | value: 0.243, 1166 | unit: "kgCO2eq", 1167 | version: ModelVersion.version_0_5_0, 1168 | }, 1169 | { 1170 | id: `fixedValue/lifeCycleAnalysisStep/distribution/shoes/intercontinental/sea-only/biofuel`, 1171 | label: 1172 | 'Emissions for intercontinental distribution of a pair of shoes (from the manufacturing plant to the local distribution warehouse, a.k.a. "upstream freight") with 100% sea-freight using biofuel', 1173 | source: 1174 | "According to studies on the GHG emissions reduction when using biofuel instead of HFO (conventional Heavy Fuel Oil for sea-freight), the reduction ranges from 40 to 93% in [one study](https://pubs.acs.org/doi/10.1021/acs.est.0c06141) and from 56 to 80% in [another](https://theicct.org/sites/default/files/publications/Marine-biofuels-sept2020.pdf). The reduction highly depends on the type of biofuel used, so for now we'll use a conservative average value of 50% reduction.\nUsing our value for conventional sea-freight: 0.243 * 0.5 = 0.1215", 1175 | value: 0.1215, 1176 | unit: "kgCO2eq", 1177 | version: ModelVersion.version_0_5_0, 1178 | }, 1179 | { 1180 | id: `fixedValue/lifeCycleAnalysisStep/distribution/shoes/intracontinental/default`, 1181 | label: 1182 | 'Emissions for the intracontinental distribution of a pair of shoes (from the manufacturing plant to the local distribution warehouse, a.k.a "upstream freight") using trucks', 1183 | source: 1184 | "2 500 km * 0.9 kg (0.7 shoes + 0.2 packaging) / 1000 kg/t * 0.06 kgCO2eq/t.km. Source for truck emission factor (0.06 kgCO2eq/t.km: https://easac.eu/fileadmin/PDF_s/reports_statements/Decarbonisation_of_Transport/EASAC_Decarbonisation_of_Transport_FINAL_March_2019.pdf).", 1185 | value: 0.135, 1186 | unit: "kgCO2eq", 1187 | version: ModelVersion.version_0_5_0, 1188 | }, 1189 | { 1190 | id: "fixedValue/lifeCycleAnalysisStep/endOfLife/shoes/withoutRecyclingProgram", 1191 | label: 1192 | "Emissions for the end of life of a pair of shoes, in case there is no known end-of-life recycling program.", 1193 | source: "ADEME BaseImpact results (TODO: detail source)", 1194 | value: 1.4, 1195 | unit: "kgCO2eq", 1196 | version: ModelVersion.version_0_5_0, 1197 | }, 1198 | { 1199 | id: "fixedValue/lifeCycleAnalysisStep/endOfLife/shoes/withRecyclingProgram", 1200 | label: 1201 | "Emissions for the end of life of a pair of shoes, in case there is a known end-of-life recycling program (a 50% bonus is applied - coefficient chosen arbitrarily by Carbonfact)", 1202 | source: "N/A", 1203 | value: 0.7, 1204 | unit: "kgCO2eq", 1205 | version: ModelVersion.version_0_5_0, 1206 | }, 1207 | { 1208 | id: "fixedValue/categoryAverageEmissions/shoes/sneakers", 1209 | label: "Average CO2e emissions for the Shoes/Sneakers category", 1210 | source: 1211 | "Using carbon footprint of a typical pair of running shoes made of synthetic materials ([source MIT](https://dspace.mit.edu/bitstream/handle/1721.1/102070/Olivetti_Manufacturing-focused.pdf)). Using this value as a proxy for the sneakers category until we can find or create data on this specific category.", 1212 | value: 14, 1213 | variationCoefficient: 0.2, 1214 | unit: "kgCO2eq", 1215 | version: ModelVersion.version_0_5_0, 1216 | }, 1217 | ]; 1218 | --------------------------------------------------------------------------------