diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..b2b3131c1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +## :wrench: Problem + +> _Describe the problem you're trying to solve, the reason why you are creating this pull request. Provide a link to the Notion card if any._ + +## :cake: Solution + +> _Explain the solution that this PR implements to solve the problem above._ + + +## :rotating_light: Points to watch/comments + +> _If there is anything unusual or some clarifications needed for the reviewer to better understand the PR, discuss it here._ + +## :desert_island: How to test + +> _What someone else than you should do to validate that the solution you implemented is working as expected. Don't hesitate to be too verbose and to explain in details, using bullet points for example, the steps to follow to test the expected behavior._ diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index d758a4031..db35ece7c 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -53,9 +53,7 @@ jobs: uses: actions/cache@v4 with: path: ~/.local/share/virtualenvs - key: ${{ runner.os }}-${{ matrix.python-version }}-pipenv-${{ hashFiles('Pipfile.lock') }} - restore-keys: | - ${{ runner.os }}-pipenv- + key: ${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-pipenv-${{ hashFiles('Pipfile.lock') }} - name: Install Node dependencies run: npm ci --prefer-offline --no-audit diff --git a/CHANGELOG.md b/CHANGELOG.md index da4586ff8..d7f1f26ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [2.3.0](https://github.com/MTES-MCT/ecobalyse/compare/v2.2.0...v2.3.0) (2024-09-25) + + +### Features + +* add link to changelog in app footer. ([#748](https://github.com/MTES-MCT/ecobalyse/issues/748)) ([efe88f5](https://github.com/MTES-MCT/ecobalyse/commit/efe88f57d4f61e74e84f62c5334a89d36fd767ee)) +* airTransportRatio should depend on durability ([#757](https://github.com/MTES-MCT/ecobalyse/issues/757)) ([a0761d1](https://github.com/MTES-MCT/ecobalyse/commit/a0761d169469fddff7b9158f21de26d96a98ae0f)) +* displayName in the textile explorer, reordered columns ([#737](https://github.com/MTES-MCT/ecobalyse/issues/737)) ([65d0ed5](https://github.com/MTES-MCT/ecobalyse/commit/65d0ed547566c193c5617147a35604e26b0bbe6d)) + + +### Bug Fixes + +* **api:** handle ingredient plane transport in food POST api. ([#769](https://github.com/MTES-MCT/ecobalyse/issues/769)) ([62587e2](https://github.com/MTES-MCT/ecobalyse/commit/62587e23593e459d66726b2221932092393790e5)) +* check db integrity after building it ([#753](https://github.com/MTES-MCT/ecobalyse/issues/753)) ([5b41ef6](https://github.com/MTES-MCT/ecobalyse/commit/5b41ef6a6e1d4e799ed2abe7f21a321a87f9ae83)) +* check uniqueness of JSON db primary keys at build time. ([#766](https://github.com/MTES-MCT/ecobalyse/issues/766)) ([0927954](https://github.com/MTES-MCT/ecobalyse/commit/0927954dfe472557f0e7c2e5e4aa24d1ce8572c2)) +* decode and validate all optionals. ([#764](https://github.com/MTES-MCT/ecobalyse/issues/764)) ([87a7c6a](https://github.com/MTES-MCT/ecobalyse/commit/87a7c6af3e6edc12c2daa36192ad7f18fdefc444)) +* encode physicalDurability parameter. ([#751](https://github.com/MTES-MCT/ecobalyse/issues/751)) ([f6750b8](https://github.com/MTES-MCT/ecobalyse/commit/f6750b8aea6dc0a4500a23465bfdbc0f0b627743)) +* fix github CI python build setup. ([#762](https://github.com/MTES-MCT/ecobalyse/issues/762)) ([ea2cd9f](https://github.com/MTES-MCT/ecobalyse/commit/ea2cd9ff566129081ccf37caef25377717933c9d)) +* fixed brightway explorer notebook error (wrong key) ([#745](https://github.com/MTES-MCT/ecobalyse/issues/745)) ([bc436c2](https://github.com/MTES-MCT/ecobalyse/commit/bc436c2d1520efb9de269cdadccf6587d1904468)) +* in brightway explorer: improve display of compartment categories, if any ([#754](https://github.com/MTES-MCT/ecobalyse/issues/754)) ([757d5a6](https://github.com/MTES-MCT/ecobalyse/commit/757d5a6b50363995011e23bd5719adedb44d296f)) +* stricter validation of POST json body passed to the textile API. ([#760](https://github.com/MTES-MCT/ecobalyse/issues/760)) ([a85bd8a](https://github.com/MTES-MCT/ecobalyse/commit/a85bd8aa506ce87b9b1310b6a4a933a591ce442e)) +* **textile:** distribution step had no inland road transports added. ([#761](https://github.com/MTES-MCT/ecobalyse/issues/761)) ([d789d7d](https://github.com/MTES-MCT/ecobalyse/commit/d789d7d63a3dede6a4c2b07d43f6f43b9328a519)) +* Update export outside of EU probability. ([#765](https://github.com/MTES-MCT/ecobalyse/issues/765)) ([c3fd9f2](https://github.com/MTES-MCT/ecobalyse/commit/c3fd9f2d5d0cc01232b31f9aa1ef657b33796292)) + ## [2.2.0](https://github.com/MTES-MCT/ecobalyse/compare/v2.1.1...v2.2.0) (2024-09-12) diff --git a/bin/build-db b/bin/build-db index bb9f4bee7..d874b3241 100755 --- a/bin/build-db +++ b/bin/build-db @@ -22,35 +22,60 @@ try { process.exit(1); } -function getJson(path) { +function parseAndValidate(path, idKeyName = "id") { const raw = JSON.parse(fs.readFileSync(path).toString()); + if (idKeyName && Array.isArray(raw)) { + try { + validatePrimaryKeys(raw, idKeyName); + } catch (err) { + console.error(`🚨 ERROR building ${path}:\n ${err}`); + process.exit(1); + } + } // Adapts a standard JSON string to what is expected to be the format // used in Elm's template strings (`"""{}"""`). return JSON.stringify(raw).replaceAll("\\", "\\\\"); } +/** + * Validates that unique identifiers are actually unique in provided datasource. + */ +function validatePrimaryKeys(records, idKeyName) { + const ids = records.map((record) => record[idKeyName]).sort(); + const duplicates = ids.filter((item, index) => ids.indexOf(item) !== index); + if (duplicates.length > 0) { + throw new Error(`Duplicate ${idKeyName}: ${duplicates}`); + } +} + const targetDbFile = "src/Static/Json.elm"; const elmTemplate = fs.readFileSync(`${targetDbFile}-template`).toString(); const elmWithFixtures = elmTemplate // Transverse JSON data - .replace("%countriesJson%", getJson("public/data/countries.json")) - .replace("%impactsJson%", getJson("public/data/impacts.json")) - .replace("%transportsJson%", getJson("public/data/transports.json")) + .replace("%countriesJson%", parseAndValidate("public/data/countries.json", "code")) + .replace("%impactsJson%", parseAndValidate("public/data/impacts.json")) + .replace("%transportsJson%", parseAndValidate("public/data/transports.json")) // Food JSON data - .replace("%foodIngredientsJson%", getJson("public/data/food/ingredients.json")) + .replace("%foodIngredientsJson%", parseAndValidate("public/data/food/ingredients.json", "id")) .replace( "%foodProcessesJson%", - getJson(NODE_ENV === "test" ? dataFiles.foodDetailed : dataFiles.foodNoDetails), + parseAndValidate(NODE_ENV === "test" ? dataFiles.foodDetailed : dataFiles.foodNoDetails, "id"), ) - .replace("%foodProductExamplesJson%", getJson("public/data/food/examples.json")) + .replace("%foodProductExamplesJson%", parseAndValidate("public/data/food/examples.json", "id")) // Textile JSON data - .replace("%textileMaterialsJson%", getJson("public/data/textile/materials.json")) + .replace("%textileMaterialsJson%", parseAndValidate("public/data/textile/materials.json", "id")) .replace( "%textileProcessesJson%", - getJson(NODE_ENV === "test" ? dataFiles.textileDetailed : dataFiles.textileNoDetails), + parseAndValidate( + NODE_ENV === "test" ? dataFiles.textileDetailed : dataFiles.textileNoDetails, + "uuid", + ), + ) + .replace( + "%textileProductExamplesJson%", + parseAndValidate("public/data/textile/examples.json", "id"), ) - .replace("%textileProductExamplesJson%", getJson("public/data/textile/examples.json")) - .replace("%textileProductsJson%", getJson("public/data/textile/products.json")); + .replace("%textileProductsJson%", parseAndValidate("public/data/textile/products.json", "id")); const header = "---- THIS FILE WAS GENERATED FROM THE FILE `Json.elm-template` BY THE `/bin/build-db` SCRIPT"; diff --git a/data/Makefile b/data/Makefile index b713cb3de..521ccbf39 100644 --- a/data/Makefile +++ b/data/Makefile @@ -47,7 +47,7 @@ compare_food: @$(call DOCKER,python3 export.py compare) format: - npm run format:json + npm run fix:all python: echo Running Python inside the container... @@ -94,4 +94,3 @@ clean_image: docker image rm $(NAME) clean: clean_data clean_image - diff --git a/data/docker/entrypoint.sh b/data/docker/entrypoint.sh index a21d800db..1544c574f 100755 --- a/data/docker/entrypoint.sh +++ b/data/docker/entrypoint.sh @@ -8,6 +8,11 @@ if [ $ECOBALYSE_ID -ne $JOVYAN_ID ]; then usermod -u $ECOBALYSE_ID jovyan fi -chown -R 1000:100 "/home/jovyan/.npm" +# Ensure .npm directory is owned by jovyan +mkdir -p /home/jovyan/.npm +chown -R jovyan:100 "/home/jovyan/.npm" -gosu jovyan "$@" +# Clear npm cache +su jovyan -c "npm cache clean --force" + +exec gosu jovyan "$@" diff --git a/data/notebooks/explore.py b/data/notebooks/explore.py index 1d190f5b0..e3c7df4e2 100644 --- a/data/notebooks/explore.py +++ b/data/notebooks/explore.py @@ -112,12 +112,14 @@ def w_csv_button(contents, columns): def display_results(database, search, limit): """display the list of search results in the w_results widget""" results = list(bw2data.Database(database).search(search, limit=limit)) + for a in results: + a["categories"] = ", ".join(a.get("categories", [])) w_results.clear_output() w_details.clear_output() w_activity.options = [("", "")] + [ ( - str(i) - + f" {a.get('name', '')} {'(' if a.get('categories') else ''}{', '.join(a.get('categories', []))}{')' if a.get('categories') else ''}", + str(i) + f" {a.get('name', '')} " + f"{('(in ' + a.get('categories', []) + ')') if a.get('categories') else ''}", a, ) for i, a in enumerate(results) @@ -128,7 +130,7 @@ def display_results(database, search, limit): display( Markdown(f"## {('+' if len(results)==LIMIT else '')}{len(results)} results") ) - columns = ["name", "code", "location"] + columns = ["name", "categories", "code", "location"] html = pandas.io.formats.style.Styler( pandas.DataFrame(results, columns=columns) ) @@ -651,7 +653,7 @@ def display_main_data(method, impact_category, activity): f"
  • Name: {input_.get('name', 'N/A')}
  • " f"
  • Code: {input_.get('code', 'N/A')}
  • " f"
  • Type: {input_.get('type', 'N/A')}
  • " - f"
  • Categories: {', '.join(input_.get('categories', 'N/A'))}
  • " + f"
  • Categories: {', '.join(input_.get('categories', []))}
  • " f"
  • CAS number: {str(input_.get('CAS number'))}
  • " f"
  • Unit: {input_.get('unit', 'N/A')}
  • " f"
  • Id: {input_.get('id', 'N/A')}
  • " @@ -720,7 +722,8 @@ def display_main_data(method, impact_category, activity): w_details.clear_output() display( Markdown( - f"# 1 {activity.get('unit', '')} of {activity.get('name', '')} ({', '.join(activity.get('categories', 'N/A'))})" + f"# 1 {activity.get('unit', '')} of {activity.get('name', '')} " + f"{('(in ' + ', '.join(activity.get('categories', [])) + ')') if activity.get('categories') else ''}" ) ) display( diff --git a/package-lock.json b/package-lock.json index bde2c5768..d0c76d854 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ecobalyse", - "version": "2.2.0", + "version": "2.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ecobalyse", - "version": "2.2.0", + "version": "2.3.0", "license": "MIT", "dependencies": { "@sentry/browser": "^8.27.0", diff --git a/package.json b/package.json index 87cafce18..3b1f0727b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ecobalyse", - "version": "2.2.0", + "version": "2.3.0", "description": "Accélérer l'affichage environnemental de la filière textile française", "author": "Ecobalyse ", "license": "MIT", diff --git a/public/data/countries.json b/public/data/countries.json index 30a5549b2..8d1318c6e 100644 --- a/public/data/countries.json +++ b/public/data/countries.json @@ -5,7 +5,6 @@ "zone": "Asia", "electricityProcessUuid": "4ee8150b0cbf3603e03345d780ba7a61", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Worst" }, @@ -15,7 +14,6 @@ "zone": "Europe", "electricityProcessUuid": "region-elec-west-europe", "heatProcessUuid": "heat-europe", - "airTransportRatio": 0, "scopes": ["textile"], "aquaticPollutionScenario": "Best" }, @@ -25,7 +23,6 @@ "zone": "Europe", "electricityProcessUuid": "2764d908c1a6fe88976d6eabf16295da", "heatProcessUuid": "heat-europe", - "airTransportRatio": 0, "scopes": ["textile"], "aquaticPollutionScenario": "Best" }, @@ -35,7 +32,6 @@ "zone": "Asia", "electricityProcessUuid": "elec-medium-region-asia", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Worst" }, @@ -45,7 +41,6 @@ "zone": "Africa", "electricityProcessUuid": "elec-medium-region-africa", "heatProcessUuid": "heat-row", - "airTransportRatio": 0, "scopes": ["textile"], "aquaticPollutionScenario": "Worst" }, @@ -55,7 +50,6 @@ "zone": "Middle_East", "electricityProcessUuid": "elec-medium-region-middle-east", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Average" }, @@ -65,7 +59,6 @@ "zone": "South_America", "electricityProcessUuid": "elec-medium-region-latin-america", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Average" }, @@ -75,7 +68,6 @@ "zone": "North_America", "electricityProcessUuid": "elec-medium-region-north-america", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Worst" }, @@ -85,7 +77,6 @@ "zone": "Oceania", "electricityProcessUuid": "53c7378e585cf74cea4837819be6e631", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Worst" }, @@ -95,7 +86,6 @@ "zone": "Asia", "electricityProcessUuid": "4968e9f8cf72cb72700d94b242048db0", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Worst" }, @@ -105,7 +95,6 @@ "zone": "Asia", "electricityProcessUuid": "0b69950358f3e8f0d3fb049d71481186", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Worst" }, @@ -115,7 +104,6 @@ "zone": "South_America", "electricityProcessUuid": "825dae104f028cb558dac151f7c45f03", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["food"], "aquaticPollutionScenario": "Worst" }, @@ -125,7 +113,6 @@ "zone": "Asia", "electricityProcessUuid": "8bbc2475141687462993329f9b7c2ddf", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["food", "textile"], "aquaticPollutionScenario": "Average" }, @@ -135,7 +122,6 @@ "zone": "Europe", "electricityProcessUuid": "80ff4bc21a0e197ea3f69d809fd8d4f1", "heatProcessUuid": "heat-europe", - "airTransportRatio": 0, "scopes": ["food"], "aquaticPollutionScenario": "Best" }, @@ -145,7 +131,6 @@ "zone": "Europe", "electricityProcessUuid": "3c131d87f8dd997d14d2ffc3477f83e6", "heatProcessUuid": "heat-europe", - "airTransportRatio": 0, "scopes": ["food", "textile"], "aquaticPollutionScenario": "Best" }, @@ -155,7 +140,6 @@ "zone": "Asia", "electricityProcessUuid": "4ee8150b0cbf3603e03345d780ba7a61", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Worst" }, @@ -165,7 +149,6 @@ "zone": "Europe", "electricityProcessUuid": "ae9240745e54987338d2228c3be2a5ec", "heatProcessUuid": "heat-europe", - "airTransportRatio": 0, "scopes": ["food"], "aquaticPollutionScenario": "Best" }, @@ -175,7 +158,6 @@ "zone": "Africa", "electricityProcessUuid": "e5f92d33532b4e649da2541b264fa363", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["food"], "aquaticPollutionScenario": "Average" }, @@ -185,7 +167,6 @@ "zone": "Asia", "electricityProcessUuid": "de2cd7848f853b1c9b8a5d7b0f255144", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Average" }, @@ -195,7 +176,6 @@ "zone": "Africa", "electricityProcessUuid": "d3287580187139b11ce76f80013510d0", "heatProcessUuid": "heat-row", - "airTransportRatio": 0, "scopes": ["food", "textile"], "aquaticPollutionScenario": "Average" }, @@ -205,7 +185,6 @@ "zone": "Oceania", "electricityProcessUuid": "609797e4710a81c9903bc72c141ff191", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["food"], "aquaticPollutionScenario": "Best" }, @@ -215,7 +194,6 @@ "zone": "South_America", "electricityProcessUuid": "debdaaba938a58ac1ab0131e47186092", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["food"], "aquaticPollutionScenario": "Worst" }, @@ -225,7 +203,6 @@ "zone": "Asia", "electricityProcessUuid": "028c9bf863a1ab77a0e636029afc5839", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Worst" }, @@ -235,7 +212,6 @@ "zone": "Africa", "electricityProcessUuid": "b42506cbed4b459815c78d741b453e8e", "heatProcessUuid": "heat-row", - "airTransportRatio": 0, "scopes": ["textile"], "aquaticPollutionScenario": "Average" }, @@ -245,7 +221,6 @@ "zone": "Middle_East", "electricityProcessUuid": "32644303e316aebb9ffa31e9856c8f6b", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Average" }, @@ -255,7 +230,6 @@ "zone": "North_America", "electricityProcessUuid": "99b549d4ba7a0f5f4ab6050d6a6689ad", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["food"], "aquaticPollutionScenario": "Best" }, @@ -265,7 +239,6 @@ "zone": "Asia", "electricityProcessUuid": "3f90d52cc1dfe32119bf53db2713c407", "heatProcessUuid": "heat-row", - "airTransportRatio": 0.33, "scopes": ["textile"], "aquaticPollutionScenario": "Average" } diff --git a/public/data/textile/examples.json b/public/data/textile/examples.json index 0e9ca887f..710b892cf 100644 --- a/public/data/textile/examples.json +++ b/public/data/textile/examples.json @@ -447,7 +447,7 @@ } }, { - "id": "f48d7a6b-68db-4d1c-9623-6ec0ab89cbdb", + "id": "6a2da9ff-9120-4a47-91ad-a8e72be45f4a", "name": "Tshirt coton (150g) - Remanufacturé", "category": "Tshirt / Polo", "query": { diff --git a/public/data/textile/processes.json b/public/data/textile/processes.json index 416c085e8..05b556354 100644 --- a/public/data/textile/processes.json +++ b/public/data/textile/processes.json @@ -718,7 +718,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.270519, + "waste": 0.21292, "alias": null }, { @@ -756,7 +756,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.270519, + "waste": 0.21292, "alias": null }, { @@ -794,7 +794,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.270519, + "waste": 0.21292, "alias": null }, { @@ -832,7 +832,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.0319569, + "waste": 0.03097, "alias": null }, { @@ -870,7 +870,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.0319569, + "waste": 0.03097, "alias": null }, { @@ -908,7 +908,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.0319569, + "waste": 0.03097, "alias": null }, { @@ -946,7 +946,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.0319569, + "waste": 0.03097, "alias": null }, { @@ -984,7 +984,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.0765978, + "waste": 0.07115, "alias": null }, { @@ -1022,7 +1022,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.538462, + "waste": 0.35, "alias": null }, { @@ -1060,7 +1060,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.538462, + "waste": 0.35, "alias": null }, { @@ -1098,7 +1098,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.201201, + "waste": 0.1675, "alias": null }, { @@ -1136,7 +1136,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.105105, + "waste": 0.09511, "alias": null }, { @@ -1174,7 +1174,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.221094, + "waste": 0.18106, "alias": null }, { @@ -1212,7 +1212,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.0582011, + "waste": 0.055, "alias": null }, { @@ -1250,7 +1250,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.631206, + "waste": 0.38696, "alias": null }, { @@ -1288,7 +1288,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 0, - "waste": 0.21716, + "waste": 0.17842, "alias": null }, { @@ -1326,7 +1326,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 8.64, - "waste": 0.0576, + "waste": 0.05446, "alias": "knitting-mix" }, { @@ -1364,7 +1364,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 6.06443, - "waste": 0.00502513, + "waste": 0.005, "alias": "knitting-fully-fashioned" }, { @@ -1402,7 +1402,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 13.2112, - "waste": 0.00502513, + "waste": 0.005, "alias": "knitting-seamless" }, { @@ -1440,7 +1440,7 @@ "heat_MJ": 0, "elec_pppm": 0.0003145, "elec_MJ": 0, - "waste": 0.0667, + "waste": 0.06253, "alias": "weaving" }, { @@ -2694,7 +2694,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 4.194, - "waste": 0.0417, + "waste": 0.04003, "alias": "knitting-straight" }, { @@ -2732,7 +2732,7 @@ "heat_MJ": 0, "elec_pppm": 0, "elec_MJ": 4.25101, - "waste": 0.0351967, + "waste": 0.034, "alias": "knitting-circular" }, { diff --git a/src/Data/Common/DecodeUtils.elm b/src/Data/Common/DecodeUtils.elm new file mode 100644 index 000000000..221d8b782 --- /dev/null +++ b/src/Data/Common/DecodeUtils.elm @@ -0,0 +1,12 @@ +module Data.Common.DecodeUtils exposing (strictOptional) + +import Json.Decode exposing (Decoder) +import Json.Decode.Extra as DE + + +{-| A stricter Decode.maybe using Json.Decode.Extra's optionalField here because we want +a failure when a Maybe decoded field value is invalid. +-} +strictOptional : String -> Decoder a -> Decoder (Maybe a -> b) -> Decoder b +strictOptional field decoder = + DE.andMap (DE.optionalField field decoder) diff --git a/src/Data/Country.elm b/src/Data/Country.elm index a8b649ba6..4334d6ade 100644 --- a/src/Data/Country.elm +++ b/src/Data/Country.elm @@ -10,6 +10,7 @@ module Data.Country exposing , encodeCode , findByCode , getAquaticPollutionRatio + , isEuropeOrTurkey , unknownCountryCode ) @@ -34,8 +35,7 @@ type AquaticPollutionScenario type alias Country = - { airTransportRatio : Split - , aquaticPollutionScenario : AquaticPollutionScenario + { aquaticPollutionScenario : AquaticPollutionScenario , code : Code , electricityProcess : Process , heatProcess : Process @@ -65,7 +65,6 @@ findByCode code = decode : List Process -> Decoder Country decode processes = Decode.succeed Country - |> Pipe.required "airTransportRatio" Split.decodeFloat |> Pipe.required "aquaticPollutionScenario" decodeAquaticPollutionScenario |> Pipe.required "code" decodeCode |> Pipe.required "electricityProcessUuid" (Process.decodeFromUuid processes) @@ -88,8 +87,7 @@ decodeList processes = encode : Country -> Encode.Value encode v = Encode.object - [ ( "airTransportRatio", Split.encodeFloat v.airTransportRatio ) - , ( "aquaticPollutionScenario", v.aquaticPollutionScenario |> aquaticPollutionScenarioToString |> Encode.string ) + [ ( "aquaticPollutionScenario", v.aquaticPollutionScenario |> aquaticPollutionScenarioToString |> Encode.string ) , ( "code", encodeCode v.code ) , ( "electricityProcessUuid", v.electricityProcess.uuid |> Process.uuidToString |> Encode.string ) , ( "heatProcessUuid", v.heatProcess.uuid |> Process.uuidToString |> Encode.string ) @@ -152,6 +150,11 @@ getAquaticPollutionRatio scenario = Split.fromPercent 65 |> Result.withDefault Split.full +isEuropeOrTurkey : Country -> Bool +isEuropeOrTurkey country = + country.zone == Zone.Europe || country.code == codeFromString "TR" + + unknownCountryCode : Code unknownCountryCode = Code "---" diff --git a/src/Data/Food/Ingredient.elm b/src/Data/Food/Ingredient.elm index 7e765aaf2..969880db0 100644 --- a/src/Data/Food/Ingredient.elm +++ b/src/Data/Food/Ingredient.elm @@ -65,11 +65,18 @@ type TransportCooling byPlaneAllowed : PlaneTransport -> Ingredient -> Result String PlaneTransport byPlaneAllowed planeTransport ingredient = - if byPlaneByDefault ingredient == PlaneNotApplicable && planeTransport /= PlaneNotApplicable then - Err byPlaneErrorMessage + case ( planeTransport, byPlaneByDefault ingredient ) of + ( ByPlane, PlaneNotApplicable ) -> + Err "Impossible de spécifier un acheminement par avion pour cet ingrédient, son origine par défaut ne le permet pas." - else - Ok planeTransport + -- Note: PlaneNotApplicable is used for conveying both the absence of air transport AND impossible plane transport; + -- here we treat it as the equivalent of a `Nothing` where the ingredient default origin would suggest a + -- transport by air (eg. Non-EU Mango) + ( PlaneNotApplicable, ByPlane ) -> + Ok ByPlane + + _ -> + Ok planeTransport byPlaneByDefault : Ingredient -> PlaneTransport @@ -81,11 +88,6 @@ byPlaneByDefault ingredient = PlaneNotApplicable -byPlaneErrorMessage : String -byPlaneErrorMessage = - "Impossible de spécifier un acheminement par avion pour cet ingrédient, son origine par défaut ne le permet pas." - - decodeId : Decode.Decoder Id decodeId = Decode.string @@ -142,7 +144,7 @@ decodeIngredient processes = |> Pipe.required "id" decodeId |> Pipe.required "inedible_part" Split.decodeFloat |> Pipe.required "name" Decode.string - |> Pipe.required "raw_to_cooked_ratio" (Unit.decodeRatio { percentage = False }) + |> Pipe.required "raw_to_cooked_ratio" Unit.decodeRatio |> Pipe.required "transport_cooling" decodeTransportCooling |> Pipe.required "visible" Decode.bool diff --git a/src/Data/Food/Process.elm b/src/Data/Food/Process.elm index a2bee15d6..b2c366ea9 100644 --- a/src/Data/Food/Process.elm +++ b/src/Data/Food/Process.elm @@ -17,6 +17,7 @@ module Data.Food.Process exposing , nameToString ) +import Data.Common.DecodeUtils as DU import Data.Impact as Impact exposing (Impacts) import Json.Decode as Decode exposing (Decoder) import Json.Decode.Extra as DE @@ -184,8 +185,8 @@ decodeProcess : Decoder Impact.Impacts -> Decoder Process decodeProcess impactsDecoder = Decode.succeed Process |> Pipe.required "category" decodeCategory - |> Pipe.optional "comment" (Decode.maybe Decode.string) Nothing - |> Pipe.optional "displayName" (Decode.maybe Decode.string) Nothing + |> DU.strictOptional "comment" Decode.string + |> DU.strictOptional "displayName" Decode.string |> Pipe.required "id" Decode.string |> Pipe.required "identifier" decodeIdentifier |> Pipe.required "impacts" impactsDecoder diff --git a/src/Data/Food/Query.elm b/src/Data/Food/Query.elm index 360643e40..5931bb8af 100644 --- a/src/Data/Food/Query.elm +++ b/src/Data/Food/Query.elm @@ -24,6 +24,7 @@ module Data.Food.Query exposing ) import Base64 +import Data.Common.DecodeUtils as DU import Data.Country as Country import Data.Food.Ingredient as Ingredient import Data.Food.Preparation as Preparation @@ -90,7 +91,7 @@ addPackaging packaging query = buildApiQuery : String -> Query -> String buildApiQuery clientUrl query = - """curl -X POST %apiUrl% \\ + """curl -sS -X POST %apiUrl% \\ -H "accept: application/json" \\ -H "content-type: application/json" \\ -d '%json%' @@ -102,11 +103,11 @@ buildApiQuery clientUrl query = decode : Decoder Query decode = Decode.succeed Query - |> Pipe.optional "distribution" (Decode.maybe Retail.decode) Nothing + |> DU.strictOptional "distribution" Retail.decode |> Pipe.required "ingredients" (Decode.list decodeIngredient) |> Pipe.optional "packaging" (Decode.list decodeProcess) [] |> Pipe.optional "preparation" (Decode.list Preparation.decodeId) [] - |> Pipe.optional "transform" (Decode.maybe decodeProcess) Nothing + |> DU.strictOptional "transform" decodeProcess decodePlaneTransport : Decoder Ingredient.PlaneTransport @@ -126,15 +127,7 @@ decodePlaneTransport = Ingredient.PlaneNotApplicable ) ) - |> Decode.map - (\maybe -> - case maybe of - Just planeTransport -> - planeTransport - - Nothing -> - Ingredient.PlaneNotApplicable - ) + |> Decode.map (Maybe.withDefault Ingredient.PlaneNotApplicable) decodeMassInGrams : Decoder Mass @@ -153,7 +146,7 @@ decodeProcess = decodeIngredient : Decoder IngredientQuery decodeIngredient = Decode.succeed IngredientQuery - |> Pipe.optional "country" (Decode.maybe Country.decodeCode) Nothing + |> DU.strictOptional "country" Country.decodeCode |> Pipe.required "id" Ingredient.decodeId |> Pipe.required "mass" decodeMassInGrams |> Pipe.optional "byPlane" decodePlaneTransport Ingredient.PlaneNotApplicable diff --git a/src/Data/Gitbook.elm b/src/Data/Gitbook.elm index 496470af3..52ed7febe 100644 --- a/src/Data/Gitbook.elm +++ b/src/Data/Gitbook.elm @@ -16,7 +16,6 @@ type Path | FoodTransformation -- Transformation des ingrédients | FoodTransport -- Transport entre étapes | FoodUse -- Consommation - | TextileAerialTransport -- Part du transport aérien textile | TextileComplementMicrofibers -- Complément textile microfibres | TextileDistribution -- Distribution textile | TextileDurability -- Durabilité textile @@ -70,9 +69,6 @@ pathToString path = FoodUse -> "alimentaire/etapes-du-cycles-de-vie/consommation" - TextileAerialTransport -> - "textile/parametres-transverses/transport#part-du-transport-aerien" - TextileComplementMicrofibers -> "textile/complements-hors-acv/microfibres" diff --git a/src/Data/Impact.elm b/src/Data/Impact.elm index b78689db3..79573e3e5 100644 --- a/src/Data/Impact.elm +++ b/src/Data/Impact.elm @@ -27,7 +27,6 @@ module Data.Impact exposing , parseTrigram , per100grams , perKg - , setEcotoxWeighting , stepsColors , stepsImpactsAsChartEntries , sumEcosystemicImpacts @@ -507,81 +506,6 @@ computeAggregatedScore definitions getter (Impacts impacts) = |> Definition.foldl (\_ -> Quantity.plus) Quantity.zero -minEcotoxWeighting : Unit.Ratio -minEcotoxWeighting = - Unit.ratio 0 - - -maxEcotoxWeighting : Unit.Ratio -maxEcotoxWeighting = - Unit.ratio 0.25 - - -{-| Set the ecotoxicity weighting (EtfC) then redistribute other Ecoscore weightings -accordingly. The methodology and formulas are described in this card: - -FIXME: ensure the card contents are moved to the public documentation eventually --} -setEcotoxWeighting : Unit.Ratio -> Definitions -> Definitions -setEcotoxWeighting (Unit.Ratio weighting) definitions = - let - defsToUpdate = - [ Definition.Acd - , Definition.Fru - , Definition.Fwe - , Definition.Ior - , Definition.Ldu - , Definition.Mru - , Definition.Ozd - , Definition.Pco - , Definition.Pma - , Definition.Swe - , Definition.Tre - , Definition.Wtu - ] - - cleanWeighting = - weighting - |> clamp (Unit.ratioToFloat minEcotoxWeighting) (Unit.ratioToFloat maxEcotoxWeighting) - in - definitions - -- Start with updating EtfC with the provided ratio - |> Definition.update Definition.EtfC - (\({ ecoscoreData } as definition) -> - { definition - | ecoscoreData = - ecoscoreData - |> Maybe.map (\data -> { data | weighting = Unit.ratio cleanWeighting }) - } - ) - -- Then redistribute the other weightings accordingly - |> Definition.map - (\trg def -> - if List.member trg defsToUpdate then - let - pefWeighting = - def.pefData - |> Maybe.map .weighting - |> Maybe.withDefault (Unit.ratio 0) - |> Unit.ratioToFloat - in - { def - | ecoscoreData = - def.ecoscoreData - |> Maybe.map - (\ecoscoreData -> - { ecoscoreData - -- = (PEF weighting for this trigram) * (78.94% - custom weighting) / 73.05% - | weighting = Unit.ratio (pefWeighting * (0.7894 - cleanWeighting) / 0.7305) - } - ) - } - - else - def - ) - - -- Parser diff --git a/src/Data/Impact/Definition.elm b/src/Data/Impact/Definition.elm index 4c1db9417..47ffa37ab 100644 --- a/src/Data/Impact/Definition.elm +++ b/src/Data/Impact/Definition.elm @@ -21,6 +21,7 @@ module Data.Impact.Definition exposing , update ) +import Data.Split as Split exposing (Split) import Data.Unit as Unit import Json.Decode as Decode exposing (Decoder) import Json.Decode.Extra as DE @@ -35,7 +36,7 @@ import Json.Encode as Encode type alias AggregatedScoreData = { color : String , normalization : Unit.Impact - , weighting : Unit.Ratio + , weighting : Split } @@ -554,7 +555,7 @@ decodeAggregatedScoreData = Decode.map3 AggregatedScoreData (Decode.field "color" Decode.string) (Decode.field "normalization" Unit.decodeImpact) - (Decode.field "weighting" (Unit.decodeRatio { percentage = True })) + (Decode.field "weighting" Split.decodeFloat) decodeDefinition : String -> Decoder Definition diff --git a/src/Data/Split.elm b/src/Data/Split.elm index e3285a276..efe676689 100644 --- a/src/Data/Split.elm +++ b/src/Data/Split.elm @@ -15,6 +15,7 @@ module Data.Split exposing , quarter , sixty , tenth + , third , thirty , toFloat , toFloatString @@ -35,6 +36,7 @@ module Data.Split exposing import FormatNumber import FormatNumber.Locales exposing (Decimals(..), frenchLocale) import Json.Decode as Decode exposing (Decoder) +import Json.Decode.Extra as DE import Json.Encode as Encode import Quantity exposing (Quantity) @@ -78,6 +80,11 @@ thirty = Split 30 +third : Split +third = + Split 33 + + fourty : Split fourty = Split 40 @@ -135,9 +142,8 @@ toFloatString = toPercentString : Int -> Split -> String -toPercentString decimals (Split float) = - float - |> FormatNumber.format { frenchLocale | decimals = Exact decimals } +toPercentString decimals = + toPercent >> FormatNumber.format { frenchLocale | decimals = Exact decimals } complement : Split -> Split @@ -163,16 +169,7 @@ divideBy input split = decodeFloat : Decoder Split decodeFloat = Decode.float - |> Decode.map fromFloat - |> Decode.andThen - (\result -> - case result of - Err error -> - Decode.fail error - - Ok split -> - Decode.succeed split - ) + |> Decode.andThen (fromFloat >> DE.fromResult) encodeFloat : Split -> Encode.Value diff --git a/src/Data/Textile/Formula.elm b/src/Data/Textile/Formula.elm index 6ae45d96c..4a5ef6043 100644 --- a/src/Data/Textile/Formula.elm +++ b/src/Data/Textile/Formula.elm @@ -9,7 +9,6 @@ module Data.Textile.Formula exposing , knittingImpacts , makingDeadStock , makingImpacts - , makingWaste , materialDyeingToxicityImpacts , materialPrintingToxicityImpacts , printingImpacts @@ -50,28 +49,19 @@ type alias StepValues = {-| Compute source mass needed and waste generated by the operation. -} -genericWaste : Unit.Ratio -> Mass -> { mass : Mass, waste : Mass } -genericWaste processWaste baseMass = +genericWaste : Split -> Mass -> { mass : Mass, waste : Mass } +genericWaste processWaste stepOutputMass = let - waste = - baseMass - |> Quantity.multiplyBy (Unit.ratioToFloat processWaste) - in - { mass = baseMass |> Quantity.plus waste, waste = waste } - - -{-| Compute source material mass needed and waste generated by the operation, according to -material & product waste data. --} -makingWaste : Split -> Mass -> { mass : Mass, waste : Mass } -makingWaste pcrWaste baseMass = - let - mass = - -- (product weight + textile waste for confection) / (1 - PCR product waste rate) - baseMass - |> Quantity.divideBy (Split.toFloat (Split.complement pcrWaste)) + stepInputMass = + -- Use input mass waste ratio formula by default + -- See https://fabrique-numerique.gitbook.io/ecobalyse/textile/cycle-de-vie-des-produits-textiles/pertes-et-rebus + -- + -- inputMass = outputMass / 1 - Input processWasteRatio + stepOutputMass + |> Quantity.divideBy (1 - Split.toFloat processWaste) in - { mass = mass, waste = Quantity.minus baseMass mass } + -- We return the inputMass of this step (considered as the output mass of the previous step) + { mass = stepInputMass, waste = Quantity.minus stepOutputMass stepInputMass } {-| Compute source material mass needed and deadstock generated by the operation, according to diff --git a/src/Data/Textile/Inputs.elm b/src/Data/Textile/Inputs.elm index bb667d593..ca03a9055 100644 --- a/src/Data/Textile/Inputs.elm +++ b/src/Data/Textile/Inputs.elm @@ -446,7 +446,7 @@ getOutOfEuropeEOLProbability materialInputs = |> getMaterialCategoryShare Origin.Synthetic in Split.fromFloat - (if Split.toPercent syntheticMaterialsShare >= 10 then + (if Split.toPercent syntheticMaterialsShare >= 50 then 0.121 else diff --git a/src/Data/Textile/Process.elm b/src/Data/Textile/Process.elm index 5fdb6c592..581f98d95 100644 --- a/src/Data/Textile/Process.elm +++ b/src/Data/Textile/Process.elm @@ -15,6 +15,7 @@ module Data.Textile.Process exposing import Data.Impact as Impact exposing (Impacts) import Data.Impact.Definition as Definition +import Data.Split as Split exposing (Split) import Data.Unit as Unit import Energy exposing (Energy) import Json.Decode as Decode exposing (Decoder) @@ -38,7 +39,7 @@ type alias Process = , stepUsage : String , unit : String , uuid : Uuid - , waste : Unit.Ratio -- share of raw material wasted when initially processed + , waste : Split -- share of raw material wasted when initially processed } @@ -106,7 +107,7 @@ decode impactsDecoder = |> Pipe.required "step_usage" Decode.string |> Pipe.required "unit" Decode.string |> Pipe.required "uuid" decodeUuid - |> Pipe.required "waste" (Unit.decodeRatio { percentage = False }) + |> Pipe.required "waste" Split.decodeFloat getDisplayName : Process -> String @@ -143,18 +144,18 @@ encodeUuid = encode : Process -> Encode.Value encode process = Encode.object - [ ( "name", Encode.string process.name ) + [ ( "alias", EncodeExtra.maybe encodeAlias process.alias ) + , ( "correctif", Encode.string process.correctif ) , ( "displayName", EncodeExtra.maybe Encode.string process.displayName ) + , ( "elec_MJ", Encode.float (Energy.inMegajoules process.elec) ) + , ( "elec_pppm", Encode.float process.elec_pppm ) + , ( "heat_MJ", Encode.float (Energy.inMegajoules process.heat) ) + , ( "impacts", Impact.encode process.impacts ) , ( "info", Encode.string process.info ) - , ( "unit", Encode.string process.unit ) + , ( "name", Encode.string process.name ) , ( "source", Encode.string process.source ) - , ( "correctif", Encode.string process.correctif ) , ( "step_usage", Encode.string process.stepUsage ) + , ( "unit", Encode.string process.unit ) , ( "uuid", encodeUuid process.uuid ) - , ( "impacts", Impact.encode process.impacts ) - , ( "heat_MJ", Encode.float (Energy.inMegajoules process.heat) ) - , ( "elec_pppm", Encode.float process.elec_pppm ) - , ( "elec_MJ", Encode.float (Energy.inMegajoules process.elec) ) - , ( "waste", Unit.encodeRatio process.waste ) - , ( "alias", EncodeExtra.maybe encodeAlias process.alias ) + , ( "waste", Split.encodeFloat process.waste ) ] diff --git a/src/Data/Textile/Query.elm b/src/Data/Textile/Query.elm index ba4a9863e..c39060790 100644 --- a/src/Data/Textile/Query.elm +++ b/src/Data/Textile/Query.elm @@ -24,6 +24,7 @@ module Data.Textile.Query exposing ) import Base64 +import Data.Common.DecodeUtils as DU import Data.Country as Country import Data.Split as Split exposing (Split) import Data.Textile.DyeingMedium as DyeingMedium exposing (DyeingMedium) @@ -98,7 +99,7 @@ addMaterial material query = buildApiQuery : String -> Query -> String buildApiQuery clientUrl query = - """curl -X POST %apiUrl% \\ + """curl -sS -X POST %apiUrl% \\ -H "accept: application/json" \\ -H "content-type: application/json" \\ -d '%json%' @@ -110,39 +111,39 @@ buildApiQuery clientUrl query = decode : Decoder Query decode = Decode.succeed Query - |> Pipe.optional "airTransportRatio" (Decode.maybe Split.decodeFloat) Nothing - |> Pipe.optional "business" (Decode.maybe Economics.decodeBusiness) Nothing - |> Pipe.optional "countryDyeing" (Decode.maybe Country.decodeCode) Nothing - |> Pipe.optional "countryFabric" (Decode.maybe Country.decodeCode) Nothing - |> Pipe.optional "countryMaking" (Decode.maybe Country.decodeCode) Nothing - |> Pipe.optional "countrySpinning" (Decode.maybe Country.decodeCode) Nothing + |> DU.strictOptional "airTransportRatio" Split.decodeFloat + |> DU.strictOptional "business" Economics.decodeBusiness + |> DU.strictOptional "countryDyeing" Country.decodeCode + |> DU.strictOptional "countryFabric" Country.decodeCode + |> DU.strictOptional "countryMaking" Country.decodeCode + |> DU.strictOptional "countrySpinning" Country.decodeCode |> Pipe.optional "disabledSteps" (Decode.list Label.decodeFromCode) [] - |> Pipe.optional "dyeingMedium" (Decode.maybe DyeingMedium.decode) Nothing - |> Pipe.optional "fabricProcess" (Decode.maybe Fabric.decode) Nothing - |> Pipe.optional "fading" (Decode.maybe Decode.bool) Nothing - |> Pipe.optional "makingComplexity" (Decode.maybe MakingComplexity.decode) Nothing - |> Pipe.optional "makingDeadStock" (Decode.maybe Split.decodeFloat) Nothing - |> Pipe.optional "makingWaste" (Decode.maybe Split.decodeFloat) Nothing + |> DU.strictOptional "dyeingMedium" DyeingMedium.decode + |> DU.strictOptional "fabricProcess" Fabric.decode + |> DU.strictOptional "fading" Decode.bool + |> DU.strictOptional "makingComplexity" MakingComplexity.decode + |> DU.strictOptional "makingDeadStock" Split.decodeFloat + |> DU.strictOptional "makingWaste" Split.decodeFloat |> Pipe.required "mass" (Decode.map Mass.kilograms Decode.float) |> Pipe.required "materials" (Decode.list decodeMaterialQuery) - |> Pipe.optional "numberOfReferences" (Decode.maybe Decode.int) Nothing - |> Pipe.optional "physicalDurability" (Decode.maybe Unit.decodePhysicalDurability) Nothing - |> Pipe.optional "price" (Decode.maybe Economics.decodePrice) Nothing - |> Pipe.optional "printing" (Decode.maybe Printing.decode) Nothing + |> DU.strictOptional "numberOfReferences" Decode.int + |> DU.strictOptional "physicalDurability" Unit.decodePhysicalDurability + |> DU.strictOptional "price" Economics.decodePrice + |> DU.strictOptional "printing" Printing.decode |> Pipe.required "product" (Decode.map Product.Id Decode.string) - |> Pipe.optional "surfaceMass" (Decode.maybe Unit.decodeSurfaceMass) Nothing - |> Pipe.optional "traceability" (Decode.maybe Decode.bool) Nothing + |> DU.strictOptional "surfaceMass" Unit.decodeSurfaceMass + |> DU.strictOptional "traceability" Decode.bool |> Pipe.optional "upcycled" Decode.bool False - |> Pipe.optional "yarnSize" (Decode.maybe Unit.decodeYarnSize) Nothing + |> DU.strictOptional "yarnSize" Unit.decodeYarnSize decodeMaterialQuery : Decoder MaterialQuery decodeMaterialQuery = Decode.succeed MaterialQuery - |> Pipe.optional "country" (Decode.maybe Country.decodeCode) Nothing + |> DU.strictOptional "country" Country.decodeCode |> Pipe.required "id" (Decode.map Material.Id Decode.string) |> Pipe.required "share" Split.decodeFloat - |> Pipe.optional "spinning" (Decode.maybe Spinning.decode) Nothing + |> DU.strictOptional "spinning" Spinning.decode encode : Query -> Encode.Value diff --git a/src/Data/Textile/Simulator.elm b/src/Data/Textile/Simulator.elm index 9b435d95a..1a881f0bc 100644 --- a/src/Data/Textile/Simulator.elm +++ b/src/Data/Textile/Simulator.elm @@ -8,6 +8,7 @@ module Data.Textile.Simulator exposing ) import Array +import Data.Country as Country import Data.Env as Env import Data.Impact as Impact exposing (Impacts) import Data.Impact.Definition as Definition @@ -133,6 +134,8 @@ compute db query = -- DURABILITY -- |> next computeDurability + -- Compute Making air transport ratio (depends on durability) - Confection + |> nextIf Label.Making computeMakingAirTransportRatio -- -- LIFECYCLE STEP IMPACTS -- @@ -210,6 +213,35 @@ computeDurability ({ inputs } as simulator) = } +computeMakingAirTransportRatio : Simulator -> Simulator +computeMakingAirTransportRatio ({ durability, inputs } as simulator) = + simulator + |> updateLifeCycleStep Label.Making + (\({ country } as step) -> + { step + | airTransportRatio = + case inputs.airTransportRatio of + Just airTransportRatio -> + -- User-provided value always takes precedence + airTransportRatio + + Nothing -> + if Country.isEuropeOrTurkey country then + -- If Making country is Europe or Turkey, airTransportRatio is always 0 + Split.zero + + else if Unit.floatDurabilityFromHolistic durability >= 1 then + -- Durable garments outside of Europe and Turkey + Split.third + + else + -- FIXME: how about falling back to country default? + -- country.airTransportRatio + Split.full + } + ) + + computeEndOfLifeImpacts : Db -> Simulator -> Simulator computeEndOfLifeImpacts { textile } simulator = simulator @@ -560,7 +592,7 @@ computeMakingStepWaste ({ inputs } as simulator) = { mass, waste } = inputs.mass - |> Formula.makingWaste + |> Formula.genericWaste (fabricProcess |> Fabric.getMakingWaste product.making.pcrWaste makingWaste ) diff --git a/src/Data/Textile/Step.elm b/src/Data/Textile/Step.elm index cc58a0bb5..bea83f048 100644 --- a/src/Data/Textile/Step.elm +++ b/src/Data/Textile/Step.elm @@ -38,6 +38,7 @@ import Data.Transport as Transport exposing (Transport) import Data.Unit as Unit import Energy exposing (Energy) import Json.Encode as Encode +import Length import Mass exposing (Mass) import Quantity import Static.Db exposing (Db) @@ -221,15 +222,14 @@ computeTransportImpacts impacts { airTransport, seaTransport } roadProcess mass computeTransportSummary : Step -> Transport -> Transport computeTransportSummary step transport = let - ( noTransports, defaultInland ) = - ( Transport.default step.transport.impacts - , Transport.default step.transport.impacts - ) + noTransports = + Transport.default step.transport.impacts in case step.label of Label.Distribution -> - -- Product Distribution leverages no transports + -- Add default road transport to materialize transport to/from a warehouse noTransports + |> Transport.add { noTransports | road = Length.kilometers 500 } Label.EndOfLife -> -- End of life leverages no transports @@ -239,10 +239,6 @@ computeTransportSummary step transport = -- Air transport only applies between the Making and the Distribution steps transport |> Formula.transportRatio step.airTransportRatio - -- Added intermediary inland transport distances to materialize - -- transport to the "distribution" step - -- Also ensure we don't add unnecessary air transport - |> Transport.add { defaultInland | air = Quantity.zero } Label.Use -> -- Product Use leverages no transports @@ -281,7 +277,7 @@ getTransportedMass inputs { label, outputMass } = updateFromInputs : Textile.Db -> Inputs -> Step -> Step updateFromInputs { wellKnown } inputs ({ label, country, complementsImpacts } as step) = let - { airTransportRatio, dyeingMedium, makingComplexity, makingDeadStock, makingWaste, printing, surfaceMass, yarnSize } = + { dyeingMedium, makingComplexity, makingDeadStock, makingWaste, printing, surfaceMass, yarnSize } = inputs in case label of @@ -348,18 +344,12 @@ updateFromInputs { wellKnown } inputs ({ label, country, complementsImpacts } as Label.Making -> { step - | airTransportRatio = - airTransportRatio |> Maybe.withDefault country.airTransportRatio - , makingComplexity = makingComplexity + | makingComplexity = makingComplexity , makingDeadStock = makingDeadStock , makingWaste = makingWaste , processInfo = { defaultProcessInfo - | airTransportRatio = - country.airTransportRatio - |> airTransportRatioToString - |> Just - , countryElec = Just country.electricityProcess.name + | countryElec = Just country.electricityProcess.name , fading = Just wellKnown.fading.name } } diff --git a/src/Data/Unit.elm b/src/Data/Unit.elm index c8e33edf9..96de32087 100644 --- a/src/Data/Unit.elm +++ b/src/Data/Unit.elm @@ -18,7 +18,6 @@ module Data.Unit exposing , encodeNonPhysicalDurability , encodePhysicalDurability , encodePickPerMeter - , encodeRatio , encodeSurfaceMass , encodeThreadDensity , encodeYarnSize @@ -63,6 +62,7 @@ module Data.Unit exposing ) import Area exposing (Area) +import Data.Split as Split exposing (Split) import Energy exposing (Energy) import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode @@ -89,29 +89,12 @@ ratioToFloat (Ratio float) = float -decodeRatio : { percentage : Bool } -> Decoder Ratio -decodeRatio { percentage } = +decodeRatio : Decoder Ratio +decodeRatio = Decode.float - |> Decode.andThen - (\float -> - if percentage && (float < 0 || float > 1) then - Decode.fail - ("Le ratio spécifié (" - ++ String.fromFloat float - ++ ") doit être compris entre 0 et 1." - ) - - else - Decode.succeed float - ) |> Decode.map ratio -encodeRatio : Ratio -> Encode.Value -encodeRatio = - ratioToFloat >> Encode.float - - -- Durability @@ -187,9 +170,8 @@ decodePhysicalDurability = ) else - Decode.succeed float + Decode.succeed (physicalDurability float) ) - |> Decode.map physicalDurability encodePhysicalDurability : PhysicalDurability -> Encode.Value @@ -427,10 +409,10 @@ impactToFloat (Quantity value) = value -impactAggregateScore : Impact -> Ratio -> Impact -> Impact +impactAggregateScore : Impact -> Split -> Impact -> Impact impactAggregateScore normalization weighting = Quantity.divideBy (impactToFloat normalization) - >> Quantity.multiplyBy (ratioToFloat weighting) + >> Quantity.multiplyBy (Split.toFloat weighting) -- Raw aggregate scores like PEF are expressed in Pt (points); we want Pts (micropoints) >> Quantity.multiplyBy 1000000 diff --git a/src/Page/Explore/Countries.elm b/src/Page/Explore/Countries.elm index dd612693b..59d27e9a1 100644 --- a/src/Page/Explore/Countries.elm +++ b/src/Page/Explore/Countries.elm @@ -68,18 +68,6 @@ table distances countries { detailed, scope } = else Nothing - , Just - { label = "Part du transport aérien" - , toValue = Table.FloatValue (.airTransportRatio >> Split.toPercent) - , toCell = - \country -> - div [ classList [ ( "text-end", not detailed ) ] ] - [ Format.splitAsPercentage 0 country.airTransportRatio - , Link.smallPillExternal - [ href (Gitbook.publicUrlFromPath Gitbook.TextileAerialTransport) ] - [ Icon.info ] - ] - } , Just { label = "Domaines" , toValue = Table.StringValue <| .scopes >> List.map Scope.toLabel >> String.join "/" diff --git a/src/Page/Explore/Impacts.elm b/src/Page/Explore/Impacts.elm index 8795a2b23..fde85a3ff 100644 --- a/src/Page/Explore/Impacts.elm +++ b/src/Page/Explore/Impacts.elm @@ -3,6 +3,7 @@ module Page.Explore.Impacts exposing (table) import Data.Dataset as Dataset import Data.Impact.Definition as Definition exposing (Definition) import Data.Scope exposing (Scope) +import Data.Split as Split import Data.Unit as Unit import Html exposing (..) import Html.Attributes exposing (..) @@ -56,9 +57,9 @@ table { detailed, scope } = , toValue = Table.FloatValue <| .pefData - >> Maybe.map (.weighting >> Unit.ratioToFloat) + >> Maybe.map (.weighting >> Split.toFloat) >> Maybe.withDefault 0 - , toCell = .pefData >> Maybe.map (.weighting >> Format.ratio) >> Maybe.withDefault (text "N/A") + , toCell = .pefData >> Maybe.map (.weighting >> Format.splitAsPercentage 2) >> Maybe.withDefault (text "N/A") } , { label = "Normalisation (Sc. Imp.)" , toValue = @@ -76,9 +77,9 @@ table { detailed, scope } = , toValue = Table.FloatValue <| .ecoscoreData - >> Maybe.map (.weighting >> Unit.ratioToFloat) + >> Maybe.map (.weighting >> Split.toFloat) >> Maybe.withDefault 0 - , toCell = .ecoscoreData >> Maybe.map (.weighting >> Format.ratio) >> Maybe.withDefault (text "N/A") + , toCell = .ecoscoreData >> Maybe.map (.weighting >> Format.splitAsPercentage 2) >> Maybe.withDefault (text "N/A") } , { label = "Description" , toValue = Table.StringValue .description diff --git a/src/Server.elm b/src/Server.elm index 8de961528..8eb04f5c4 100644 --- a/src/Server.elm +++ b/src/Server.elm @@ -19,7 +19,6 @@ import Data.Textile.Material as Material exposing (Material) import Data.Textile.Product as TextileProduct exposing (Product) import Data.Textile.Query as TextileQuery import Data.Textile.Simulator as Simulator exposing (Simulator) -import Json.Decode as Decode import Json.Encode as Encode import Route as WebRoute import Server.Query as Query @@ -277,41 +276,40 @@ handleRequest db request = |> respondWith 400 -- POST routes - Just Route.FoodPostRecipe -> - request.body - |> handleDecodeBody BuilderQuery.decode - (\query -> - executeFoodQuery db (toFoodResults query) query - ) - - Just Route.TextilePostSimulator -> - request.body - |> handleDecodeBody TextileQuery.decode - (executeTextileQuery db toAllImpactsSimple) - - Just Route.TextilePostSimulatorDetailed -> - request.body - |> handleDecodeBody TextileQuery.decode - (executeTextileQuery db Simulator.encode) - - Just (Route.TextilePostSimulatorSingle trigram) -> - request.body - |> handleDecodeBody TextileQuery.decode - (executeTextileQuery db (toSingleImpactSimple trigram)) + Just (Route.FoodPostRecipe (Ok foodQuery)) -> + executeFoodQuery db (toFoodResults foodQuery) foodQuery - Nothing -> - encodeStringError "Endpoint doesn't exist" - |> respondWith 404 + Just (Route.FoodPostRecipe (Err error)) -> + Encode.string error + |> respondWith 400 + Just (Route.TextilePostSimulator (Ok textileQuery)) -> + textileQuery + |> executeTextileQuery db toAllImpactsSimple -handleDecodeBody : Decode.Decoder a -> (a -> JsonResponse) -> Encode.Value -> JsonResponse -handleDecodeBody decoder mapper jsonBody = - case Decode.decodeValue decoder jsonBody of - Err error -> - ( 400, Encode.string (Decode.errorToString error) ) + Just (Route.TextilePostSimulator (Err error)) -> + Encode.string error + |> respondWith 400 + + Just (Route.TextilePostSimulatorDetailed (Ok textileQuery)) -> + textileQuery + |> executeTextileQuery db Simulator.encode - Ok x -> - mapper x + Just (Route.TextilePostSimulatorDetailed (Err error)) -> + Encode.string error + |> respondWith 400 + + Just (Route.TextilePostSimulatorSingle (Ok textileQuery) trigram) -> + textileQuery + |> executeTextileQuery db (toSingleImpactSimple trigram) + + Just (Route.TextilePostSimulatorSingle (Err error) _) -> + Encode.string error + |> respondWith 400 + + Nothing -> + encodeStringError "Endpoint doesn't exist" + |> respondWith 404 update : Msg -> Cmd Msg diff --git a/src/Server/Query.elm b/src/Server/Query.elm index 393c61677..68e2420ed 100644 --- a/src/Server/Query.elm +++ b/src/Server/Query.elm @@ -15,7 +15,6 @@ import Data.Food.Query as BuilderQuery import Data.Food.Retail as Retail exposing (Distribution) import Data.Scope as Scope exposing (Scope) import Data.Split as Split exposing (Split) -import Data.Textile.Db as Textile import Data.Textile.DyeingMedium as DyeingMedium exposing (DyeingMedium) import Data.Textile.Economics as Economics import Data.Textile.Fabric as Fabric exposing (Fabric) @@ -33,6 +32,7 @@ import Mass exposing (Mass) import Quantity import Regex import Result.Extra as RE +import Static.Db exposing (Db) import Url.Parser.Query as Query exposing (Parser) @@ -63,8 +63,8 @@ succeed = always >> Query.custom "" -parseFoodQuery : List Country -> Food.Db -> Parser (Result Errors BuilderQuery.Query) -parseFoodQuery countries food = +parseFoodQuery : Db -> Parser (Result Errors BuilderQuery.Query) +parseFoodQuery { countries, food } = succeed (Ok BuilderQuery.Query) |> apply (distributionParser "distribution") |> apply (ingredientListParser "ingredients" countries food) @@ -281,9 +281,8 @@ validatePhysicalDurability string = ) else - Ok durability + Ok (Unit.PhysicalDurability durability) ) - |> Result.map Unit.PhysicalDurability maybeTransformParser : String -> List FoodProcess.Process -> Parser (ParseResult (Maybe BuilderQuery.ProcessQuery)) @@ -384,8 +383,8 @@ parseTransform_ transforms string = Err <| "Format de procédé de transformation invalide : " ++ string ++ "." -parseTextileQuery : List Country -> Textile.Db -> Parser (Result Errors TextileQuery.Query) -parseTextileQuery countries textile = +parseTextileQuery : Db -> Parser (Result Errors TextileQuery.Query) +parseTextileQuery { countries, textile } = succeed (Ok TextileQuery.Query) |> apply (maybeSplitParser "airTransportRatio") |> apply (maybeBusiness "business") @@ -884,7 +883,6 @@ encodeErrors : Errors -> Encode.Value encodeErrors errors = Encode.object [ ( "errors" - , errors - |> Encode.dict identity Encode.string + , errors |> Encode.dict identity Encode.string ) ] diff --git a/src/Server/Route.elm b/src/Server/Route.elm index 34f519a64..6124d5951 100644 --- a/src/Server/Route.elm +++ b/src/Server/Route.elm @@ -3,13 +3,13 @@ module Server.Route exposing , endpoint ) -import Data.Country exposing (Country) -import Data.Food.Db as Food import Data.Food.Query as BuilderQuery import Data.Impact as Impact import Data.Impact.Definition as Definition -import Data.Textile.Db as Textile +import Data.Textile.Inputs as Inputs import Data.Textile.Query as TextileQuery +import Json.Decode as Decode +import Json.Encode as Encode import Server.Query as Query import Server.Request exposing (Request) import Static.Db exposing (Db) @@ -38,7 +38,7 @@ type Route | FoodGetTransformList -- POST -- Food recipe builder (POST, JSON body) - | FoodPostRecipe + | FoodPostRecipe (Result String BuilderQuery.Query) -- -- Textile Routes -- GET @@ -56,42 +56,73 @@ type Route | TextileGetSimulatorSingle Definition.Trigram (Result Query.Errors TextileQuery.Query) -- POST -- Textile Simple version of all impacts (POST, JSON body) - | TextilePostSimulator + | TextilePostSimulator (Result String TextileQuery.Query) -- Textile Detailed version for all impacts (POST, JSON body) - | TextilePostSimulatorDetailed - -- Textile Simple version for one specific impact (POST, JSON bosy) - | TextilePostSimulatorSingle Definition.Trigram + | TextilePostSimulatorDetailed (Result String TextileQuery.Query) + -- Textile Simple version for one specific impact (POST, JSON body) + | TextilePostSimulatorSingle (Result String TextileQuery.Query) Definition.Trigram -parser : Food.Db -> Textile.Db -> List Country -> Parser (Route -> a) a -parser foodDb textile countries = +parser : Db -> Encode.Value -> Parser (Route -> a) a +parser db body = Parser.oneOf [ -- Food - Parser.map FoodGetCountryList (s "GET" s "food" s "countries") - , Parser.map FoodGetIngredientList (s "GET" s "food" s "ingredients") - , Parser.map FoodGetTransformList (s "GET" s "food" s "transforms") - , Parser.map FoodGetPackagingList (s "GET" s "food" s "packagings") - , Parser.map FoodGetRecipe (s "GET" s "food" Query.parseFoodQuery countries foodDb) - , Parser.map FoodPostRecipe (s "POST" s "food") + -- GET + (s "GET" s "food" s "countries") + |> Parser.map FoodGetCountryList + , (s "GET" s "food" s "ingredients") + |> Parser.map FoodGetIngredientList + , (s "GET" s "food" s "transforms") + |> Parser.map FoodGetTransformList + , (s "GET" s "food" s "packagings") + |> Parser.map FoodGetPackagingList + , (s "GET" s "food" Query.parseFoodQuery db) + |> Parser.map FoodGetRecipe + , (s "POST" s "food") + |> Parser.map (FoodPostRecipe (decodeFoodQueryBody body)) -- Textile - , Parser.map TextileGetCountryList (s "GET" s "textile" s "countries") - , Parser.map TextileGetMaterialList (s "GET" s "textile" s "materials") - , Parser.map TextileGetProductList (s "GET" s "textile" s "products") - , Parser.map TextileGetSimulator (s "GET" s "textile" s "simulator" Query.parseTextileQuery countries textile) - , Parser.map TextileGetSimulatorDetailed (s "GET" s "textile" s "simulator" s "detailed" Query.parseTextileQuery countries textile) - , Parser.map TextileGetSimulatorSingle (s "GET" s "textile" s "simulator" Impact.parseTrigram Query.parseTextileQuery countries textile) - , Parser.map TextilePostSimulator (s "POST" s "textile" s "simulator") - , Parser.map TextilePostSimulatorDetailed (s "POST" s "textile" s "simulator" s "detailed") - , Parser.map TextilePostSimulatorSingle (s "POST" s "textile" s "simulator" Impact.parseTrigram) + , (s "GET" s "textile" s "countries") + |> Parser.map TextileGetCountryList + , (s "GET" s "textile" s "materials") + |> Parser.map TextileGetMaterialList + , (s "GET" s "textile" s "products") + |> Parser.map TextileGetProductList + , (s "GET" s "textile" s "simulator" Query.parseTextileQuery db) + |> Parser.map TextileGetSimulator + , (s "GET" s "textile" s "simulator" s "detailed" Query.parseTextileQuery db) + |> Parser.map TextileGetSimulatorDetailed + , (s "GET" s "textile" s "simulator" Impact.parseTrigram Query.parseTextileQuery db) + |> Parser.map TextileGetSimulatorSingle + , (s "POST" s "textile" s "simulator") + |> Parser.map (TextilePostSimulator (decodeTextileQueryBody db body)) + , (s "POST" s "textile" s "simulator" s "detailed") + |> Parser.map (TextilePostSimulatorDetailed (decodeTextileQueryBody db body)) + , (s "POST" s "textile" s "simulator" Impact.parseTrigram) + |> Parser.map (TextilePostSimulatorSingle (decodeTextileQueryBody db body)) ] +decodeFoodQueryBody : Encode.Value -> Result String BuilderQuery.Query +decodeFoodQueryBody = + Decode.decodeValue BuilderQuery.decode + >> Result.mapError Decode.errorToString + + +decodeTextileQueryBody : Db -> Encode.Value -> Result String TextileQuery.Query +decodeTextileQueryBody db = + Decode.decodeValue TextileQuery.decode + >> Result.mapError Decode.errorToString + -- Note: Using inputs mapping to act as query validation + >> Result.andThen (Inputs.fromQuery db) + >> Result.map Inputs.toQuery + + endpoint : Db -> Request -> Maybe Route -endpoint { countries, food, textile } { method, url } = +endpoint db { body, method, url } = -- Notes: -- - Url.fromString can't build a Url without a fully qualified URL, so as we only have the -- request path from Express, we build a fake URL with a fake protocol and hostname. -- - We update the path appending the HTTP method to it, for simpler, cheaper route parsing. Url.fromString ("http://x/" ++ method ++ url) - |> Maybe.andThen (Parser.parse (parser food textile countries)) + |> Maybe.andThen (Parser.parse (parser db body)) diff --git a/src/Views/Format.elm b/src/Views/Format.elm index b3f7f8655..5955d71d0 100644 --- a/src/Views/Format.elm +++ b/src/Views/Format.elm @@ -16,7 +16,6 @@ module Views.Format exposing , percent , picking , priceInEUR - , ratio , splitAsFloat , splitAsPercentage , squareMeters @@ -213,17 +212,6 @@ yarnSize = Unit.yarnSizeInKilometers >> formatRichFloat 0 "Nm" -ratio : Unit.Ratio -> Html msg -ratio = - ratioToDecimals 2 - - -ratioToDecimals : Int -> Unit.Ratio -> Html msg -ratioToDecimals decimals (Unit.Ratio float) = - (float * 100) - |> formatRichFloat decimals "%" - - splitAsFloat : Int -> Split -> Html msg splitAsFloat int value = Split.toFloat value diff --git a/tests/Data/ImpactTest.elm b/tests/Data/ImpactTest.elm index 390622c20..b788d73f8 100644 --- a/tests/Data/ImpactTest.elm +++ b/tests/Data/ImpactTest.elm @@ -2,6 +2,7 @@ module Data.ImpactTest exposing (..) import Data.Impact as Impact import Data.Impact.Definition as Definition +import Data.Split as Split import Data.Unit as Unit import Expect import Mass @@ -99,7 +100,7 @@ suite = |> List.map (\trigram -> Definition.get trigram db.definitions) |> List.filterMap .ecoscoreData |> List.map .weighting - |> List.map Unit.ratioToFloat + |> List.map Split.toFloat |> List.sum |> Expect.within (Expect.Absolute 0.01) 1 |> asTest "should be 1" @@ -109,34 +110,10 @@ suite = |> List.map (\trigram -> Definition.get trigram db.definitions) |> List.filterMap .pefData |> List.map .weighting - |> List.map Unit.ratioToFloat + |> List.map Split.toFloat |> List.sum |> Expect.within (Expect.Absolute 0.01) 1 |> asTest "should be 1" ] - , describe "setEcotoxWeighting" - [ db.definitions - |> Impact.setEcotoxWeighting (Unit.ratio 0) - |> Definition.get Definition.Acd - |> .ecoscoreData - |> Maybe.map (.weighting >> Unit.ratioToFloat) - |> Maybe.withDefault -99 - |> Expect.within (Expect.Absolute 0.001) 0.067 - |> asTest "should update other weightings" - , db.definitions - |> Impact.setEcotoxWeighting (Unit.ratio 0) - |> Definition.toList - |> List.filterMap (.ecoscoreData >> Maybe.map (.weighting >> Unit.ratioToFloat)) - |> List.sum - |> Expect.within (Expect.Absolute 0.001) 1 - |> asTest "should sum all Ecs weightings to 100% when EtfC is set to 0" - , db.definitions - |> Impact.setEcotoxWeighting (Unit.ratio 0.25) - |> Definition.toList - |> List.filterMap (.ecoscoreData >> Maybe.map (.weighting >> Unit.ratioToFloat)) - |> List.sum - |> Expect.within (Expect.Absolute 0.001) 1 - |> asTest "should sum all Ecs weightings to 100% when EtfC is set to 25" - ] ] ) diff --git a/tests/Data/Textile/FormulaTest.elm b/tests/Data/Textile/FormulaTest.elm index 3665eb21e..216e02f70 100644 --- a/tests/Data/Textile/FormulaTest.elm +++ b/tests/Data/Textile/FormulaTest.elm @@ -28,20 +28,20 @@ km = noOpProcess : Process noOpProcess = - { name = "Default" + { alias = Nothing + , correctif = "" , displayName = Just "Default" + , elec = Energy.megajoules 0 + , elec_pppm = 0 + , heat = Energy.megajoules 0 + , impacts = Impact.empty , info = "" - , unit = "" - , uuid = Process.Uuid "" + , name = "Default" , source = "" - , correctif = "" , stepUsage = "" - , impacts = Impact.empty - , heat = Energy.megajoules 0 - , elec_pppm = 0 - , elec = Energy.megajoules 0 - , waste = Unit.ratio 0 - , alias = Nothing + , unit = "" + , uuid = Process.Uuid "" + , waste = Split.zero } @@ -57,15 +57,9 @@ suite = in [ describe "Formula.genericWaste" [ kg 1 - |> Formula.genericWaste (Unit.ratio 0.5) - |> Expect.equal { mass = kg 1.5, waste = kg 0.5 } - |> asTest "should compute material waste" - ] - , describe "Formula.makingWaste" - [ kg 1 - |> Formula.makingWaste Split.half + |> Formula.genericWaste Split.half |> Expect.equal { mass = kg 2, waste = kg 1 } - |> asTest "should compute material waste from material and product waste data" + |> asTest "should compute generic waste using input waste ratio" ] , describe "Formula.makingDeadStock" [ kg 1 diff --git a/tests/Data/Textile/LifeCycleTest.elm b/tests/Data/Textile/LifeCycleTest.elm index c1b588124..1f9d74521 100644 --- a/tests/Data/Textile/LifeCycleTest.elm +++ b/tests/Data/Textile/LifeCycleTest.elm @@ -31,7 +31,7 @@ suite = |> Result.andThen (lifeCycleToTransports db tShirtCotonFrance) |> Result.map LifeCycle.computeTotalTransportImpacts |> Result.map (\{ road, sea } -> ( Length.inKilometers road, Length.inKilometers sea )) - |> Expect.equal (Ok ( 2000, 21549 )) + |> Expect.equal (Ok ( 2500, 21549 )) |> asTest "should compute default distances" , let tShirtCotonEnnoblementIndia = @@ -46,7 +46,7 @@ suite = |> Result.andThen (lifeCycleToTransports db tShirtCotonEnnoblementIndia) |> Result.map LifeCycle.computeTotalTransportImpacts |> Result.map (\{ road, sea } -> ( Length.inKilometers road, Length.inKilometers sea )) - |> Expect.equal (Ok ( 1000, 45471 )) + |> Expect.equal (Ok ( 1500, 45471 )) |> asTest "should compute custom distances" ] ] diff --git a/tests/Data/Textile/SimulatorTest.elm b/tests/Data/Textile/SimulatorTest.elm index 88c1eab74..dc8d39a7b 100644 --- a/tests/Data/Textile/SimulatorTest.elm +++ b/tests/Data/Textile/SimulatorTest.elm @@ -1,7 +1,10 @@ module Data.Textile.SimulatorTest exposing (..) +import Data.Country as Country import Data.Impact as Impact import Data.Impact.Definition as Definition +import Data.Split as Split +import Data.Textile.Economics as Economics import Data.Textile.LifeCycle as LifeCycle import Data.Textile.Query exposing (Query, tShirtCotonFrance) import Data.Textile.Simulator as Simulator @@ -47,7 +50,7 @@ suite = [ { tShirtCotonFrance | countrySpinning = Nothing } - |> expectImpact db ecs 1469.1531378179673 + |> expectImpact db ecs 1473.9780976999311 |> asTest "should compute a simulation ecs impact" , describe "disabled steps" [ { tShirtCotonFrance | disabledSteps = [ Label.Ennobling ] } @@ -81,6 +84,69 @@ suite = ) ] ] + , let + tShirtCotonWithSmallerPhysicalDurability = + { tShirtCotonFrance + | numberOfReferences = Just 10 + , price = Just <| Economics.priceFromFloat 100 + , physicalDurability = Just <| Unit.physicalDurability 1 + } + in + describe "compute holistic durability" + [ tShirtCotonFrance + |> Simulator.compute db + |> Result.map .durability + |> Expect.equal (Ok { physical = Unit.physicalDurability 1.45, nonPhysical = Unit.nonPhysicalDurability 0.67 }) + |> asTest "should have default durability" + , { physical = Unit.physicalDurability 1.45, nonPhysical = Unit.nonPhysicalDurability 0.67 } + |> Unit.floatDurabilityFromHolistic + |> Expect.within (Expect.Absolute 0.001) 0.67 + |> asTest "should take the min of the two durabilities" + , tShirtCotonWithSmallerPhysicalDurability + |> Simulator.compute db + |> Result.map .durability + |> Expect.equal (Ok { physical = Unit.physicalDurability 1, nonPhysical = Unit.nonPhysicalDurability 1.19 }) + |> asTest "should take into account when non physical durability changes" + , tShirtCotonWithSmallerPhysicalDurability + |> Simulator.compute db + |> Result.map (.durability >> Unit.floatDurabilityFromHolistic) + |> Expect.equal (Ok 1) + |> asTest "should return non physical durability if it is the smallest" + ] + , let + tShirtCotonWithSmallerPhysicalDurabilityCn = + { tShirtCotonFrance + | numberOfReferences = Just 10 + , price = Just <| Economics.priceFromFloat 100 + , physicalDurability = Just <| Unit.physicalDurability 1.1 + , countryMaking = Just (Country.Code "CN") + } + in + describe "compute airTransporRatio" + [ tShirtCotonFrance + |> Simulator.compute db + |> Result.map (.lifeCycle >> LifeCycle.getStepProp Label.Making .airTransportRatio Split.half) + |> Expect.equal (Ok Split.zero) + |> asTest "should be zero for products from Europe or Turkey" + , { tShirtCotonFrance | countryMaking = Just (Country.Code "CN") } + |> Simulator.compute db + |> Result.map (.lifeCycle >> LifeCycle.getStepProp Label.Making .airTransportRatio Split.half) + |> Expect.equal (Ok Split.full) + |> asTest "should be full for products not coming from Europe or Turkey" + , tShirtCotonWithSmallerPhysicalDurabilityCn + |> Simulator.compute db + |> Result.map (.lifeCycle >> LifeCycle.getStepProp Label.Making .airTransportRatio Split.half) + |> Expect.equal (Ok Split.third) + |> asTest "should be 0.33 for products not coming from Europe or Turkey but with a durability >= 1" + , { tShirtCotonFrance + | countryMaking = Just (Country.Code "CN") + , airTransportRatio = Just Split.two + } + |> Simulator.compute db + |> Result.map (.lifeCycle >> LifeCycle.getStepProp Label.Making .airTransportRatio Split.half) + |> Expect.equal (Ok Split.two) + |> asTest "should keep the user provided value" + ] , describe "getTotalImpactsWithoutComplements" [ tShirtCotonFrance |> Simulator.compute db diff --git a/tests/Data/UnitTest.elm b/tests/Data/UnitTest.elm index f52fce313..a364b4fdf 100644 --- a/tests/Data/UnitTest.elm +++ b/tests/Data/UnitTest.elm @@ -1,5 +1,6 @@ module Data.UnitTest exposing (..) +import Data.Split as Split import Data.Unit as Unit import Energy import Expect exposing (Expectation) @@ -24,15 +25,6 @@ suite = |> Result.mapError Decode.errorToString |> Expect.err |> asTest "should discard erroneous Durability value" - , "1.1" - |> Decode.decodeString (Unit.decodeRatio { percentage = True }) - |> Result.mapError Decode.errorToString - |> Expect.err - |> asTest "should discard erroneous Ratio value" - , "1.1" - |> Decode.decodeString (Unit.decodeRatio { percentage = False }) - |> Expect.ok - |> asTest "should not discard a non-percentage value" , "8868687687" |> Decode.decodeString Unit.decodeSurfaceMass |> Result.mapError Decode.errorToString @@ -48,15 +40,15 @@ suite = ] , describe "Unit.impactAggregateScore" [ Unit.impact 1 - |> Unit.impactAggregateScore (Unit.impact 1) (Unit.ratio 1) + |> Unit.impactAggregateScore (Unit.impact 1) Split.full |> Expect.equal (Unit.impact 1000000) |> asTest "should compute impact aggregate score (1, 1)" , Unit.impact 1 - |> Unit.impactAggregateScore (Unit.impact 2) (Unit.ratio 0.5) + |> Unit.impactAggregateScore (Unit.impact 2) Split.half |> Expect.equal (Unit.impact 250000) |> asTest "should compute impact aggregate score (1, 0.5)" , Unit.impact 1 - |> Unit.impactAggregateScore (Unit.impact 0.25) (Unit.ratio 0.75) + |> Unit.impactAggregateScore (Unit.impact 0.25) (Split.fromFloat 0.75 |> Result.withDefault Split.zero) |> Expect.equal (Unit.impact 3000000) |> asTest "should compute impact aggregate score (0.25, 0.75)" ] diff --git a/tests/Server/RouteTest.elm b/tests/Server/RouteTest.elm index 9ea98dab0..5dfc26eac 100644 --- a/tests/Server/RouteTest.elm +++ b/tests/Server/RouteTest.elm @@ -12,6 +12,7 @@ import Data.Textile.Material.Spinning as Spinning import Data.Textile.Process as TextileProcess import Data.Textile.Query as Query exposing (Query, tShirtCotonFrance) import Data.Textile.Step.Label as Label +import Data.Unit as Unit import Dict exposing (Dict) import Expect import Json.Encode as Encode @@ -75,12 +76,12 @@ foodEndpoints db = , describe "POST endpoints" [ "/food" |> testEndpoint db "POST" (FoodQuery.encode FoodQuery.empty) - |> Expect.equal (Just Route.FoodPostRecipe) + |> Expect.equal (Just (Route.FoodPostRecipe (Ok FoodQuery.empty))) |> asTest "should map the POST /food endpoint" , "/food" |> testEndpoint db "POST" Encode.null - |> Expect.equal (Just Route.FoodPostRecipe) - |> asTest "should map the POST /food endpoint whatever the request body is" + |> expectFoodSingleErrorContains "ingredients" + |> asTest "should fail on invalid query passed" ] , describe "validation" [ testEndpoint db "GET" Encode.null "/food?ingredients[]=egg-indoor-code3|0" @@ -216,12 +217,49 @@ textileEndpoints db = , describe "POST endpoints" [ "/textile/simulator" |> testEndpoint db "POST" (Query.encode tShirtCotonFrance) - |> Expect.equal (Just Route.TextilePostSimulator) - |> asTest "should map the POST /textile/simulator endpoint" + |> Expect.equal (Just (Route.TextilePostSimulator (Ok tShirtCotonFrance))) + |> asTest "should map the POST /textile/simulator endpoint with the body parsed as a valid query" , "/textile/simulator" |> testEndpoint db "POST" Encode.null - |> Expect.equal (Just Route.TextilePostSimulator) - |> asTest "should map the POST /textile/simulator endpoint whatever the request body is" + |> Expect.equal (Just (Route.TextilePostSimulator (Err "Problem with the given value:\n\nnull\n\nExpecting an OBJECT with a field named `product`"))) + |> asTest "should map the POST /textile/simulator endpoint with an error when json body is invalid" + , "/textile/simulator" + |> testEndpoint db + "POST" + (Query.encode + { tShirtCotonFrance + | physicalDurability = + Just <| Unit.physicalDurability 9900000 + } + ) + |> expectTextileSingleErrorContains "physicalDurability" + |> asTest "should reject invalid physicalDurability" + , "/textile/simulator" + |> testEndpoint db + "POST" + (Query.encode + { tShirtCotonFrance + | countrySpinning = Just (Country.Code "invalid") + } + ) + |> Expect.equal (Just (Route.TextilePostSimulator (Err "Code pays invalide: invalid."))) + |> asTest "should reject invalid spinning country" + , "/textile/simulator" + |> testEndpoint db + "POST" + (Query.encode + { tShirtCotonFrance + | materials = + [ { country = Just (Country.Code "invalid") + , id = Material.Id "ei-coton" + , share = Split.full + , spinning = Nothing + } + ] + } + ) + |> Expect.equal (Just (Route.TextilePostSimulator (Err "Code pays invalide: invalid."))) + |> asTest "should reject invalid materials country" ] , describe "materials param checks" [ let @@ -394,7 +432,14 @@ testEndpoint dbs method body url = { method = method , url = url , body = body - , processes = { foodProcesses = Encode.list FoodProcess.encode dbs.food.processes |> Encode.encode 0, textileProcesses = Encode.list TextileProcess.encode dbs.textile.processes |> Encode.encode 0 } + , processes = + { foodProcesses = + Encode.list FoodProcess.encode dbs.food.processes + |> Encode.encode 0 + , textileProcesses = + Encode.list TextileProcess.encode dbs.textile.processes + |> Encode.encode 0 + } , jsResponseHandler = Encode.null } @@ -427,3 +472,23 @@ extractTextileErrors route = _ -> Nothing + + +expectFoodSingleErrorContains : String -> Maybe Route.Route -> Expect.Expectation +expectFoodSingleErrorContains str route = + case route of + Just (Route.FoodPostRecipe (Err err)) -> + Expect.equal (String.contains str err) True + + _ -> + Expect.fail "No matching error found" + + +expectTextileSingleErrorContains : String -> Maybe Route.Route -> Expect.Expectation +expectTextileSingleErrorContains str route = + case route of + Just (Route.TextilePostSimulator (Err err)) -> + Expect.equal (String.contains str err) True + + _ -> + Expect.fail "No matching error found" diff --git a/tests/e2e-food.json b/tests/e2e-food.json index 216d0181f..82c6fe328 100644 --- a/tests/e2e-food.json +++ b/tests/e2e-food.json @@ -28,7 +28,7 @@ "scoring": { "all": 1384.2968296621593, "climate": 517.6798374796787, - "biodiversity": 430.8042070391977, + "biodiversity": 430.80420703919776, "health": 139.12974375280868, "resources": 296.6830413904739 } @@ -164,7 +164,7 @@ "scoring": { "all": 136.84213596200448, "climate": 22.327421210725763, - "biodiversity": 64.54418913083381, + "biodiversity": 64.54418913083383, "health": 14.535423007764042, "resources": 35.43510261268088 } diff --git a/tests/e2e-textile.json b/tests/e2e-textile.json index 72612dcb2..ee03eaee0 100644 --- a/tests/e2e-textile.json +++ b/tests/e2e-textile.json @@ -10,27 +10,27 @@ "countryMaking=FR" ], "impacts": { - "acd": 0.0414446685749946, - "cch": 6.048874393344061, - "etf": 133.45735497327883, - "etf-c": 281.03257869705175, - "fru": 108.41610613408756, - "fwe": 0.0015967563755970645, - "htc": 9.202232406694513e-10, - "htc-c": 1.5167242473017484e-9, - "htn": 2.2684998010224684e-8, - "htn-c": 3.378964481489889e-8, - "ior": 13.313345405119325, - "ldu": 105.51839908615344, - "mru": 0.000052078700426912154, - "ozd": 0.000030370478290087493, - "pco": 0.016994499640779308, - "pma": 3.6240125804991626e-7, - "swe": 0.02536100942287826, - "tre": 0.12574953428356123, - "wtu": 13.97456474348671, - "ecs": 1469.1531378179673, - "pef": 939.757440532827 + "acd": 0.04163544680574335, + "cch": 6.076074899448809, + "etf": 134.0863054530789, + "etf-c": 282.1219011261312, + "fru": 108.75723225108744, + "fwe": 0.0016020599526329704, + "htc": 9.324883616149098e-10, + "htc-c": 1.5316938071499886e-9, + "htn": 2.2958539136038225e-8, + "htn-c": 3.396278638169421e-8, + "ior": 13.313836563271293, + "ldu": 106.0312189737267, + "mru": 0.000052176453397729595, + "ozd": 0.00003037113615558597, + "pco": 0.017158026522891636, + "pma": 3.650629404142302e-7, + "swe": 0.025467662607441162, + "tre": 0.12648609991631055, + "wtu": 14.022971246600134, + "ecs": 1473.9780976999311, + "pef": 942.9643784896306 } }, { @@ -45,27 +45,27 @@ "countryMaking=FR" ], "impacts": { - "acd": 0.03999283350554488, - "cch": 5.591496973560117, - "etf": 132.02731216356787, - "etf-c": 279.6313964116634, - "fru": 105.51519866597374, - "fwe": 0.0021399640093722767, - "htc": 9.132619864558306e-10, - "htc-c": 1.493391231958824e-9, - "htn": 2.2556947321763515e-8, - "htn-c": 3.3337697852860114e-8, - "ior": 13.299228897821145, - "ldu": 103.94093137151837, - "mru": 0.00005200935295978153, - "ozd": 0.000030381656157113567, - "pco": 0.015366462287409441, - "pma": 2.9198939677060944e-7, - "swe": 0.024843458237795588, - "tre": 0.11892004198055697, - "wtu": 14.11834524471008, - "ecs": 1445.545062184085, - "pef": 916.5851663113473 + "acd": 0.04017877886165545, + "cch": 5.617174959801102, + "etf": 132.65150231089498, + "etf-c": 280.7160545793037, + "fru": 105.84666823008729, + "fwe": 0.00214707581824648, + "htc": 9.255039347492112e-10, + "htc-c": 1.5082831207682146e-9, + "htn": 2.2830062191910908e-8, + "htn-c": 3.3509334976722056e-8, + "ior": 13.299673064884704, + "ldu": 104.44850017797987, + "mru": 0.000052106875086460975, + "ozd": 0.000030382351231542455, + "pco": 0.015524569751985531, + "pma": 2.944166918372446e-7, + "swe": 0.02494838859581367, + "tre": 0.1196338735708263, + "wtu": 14.167230364956025, + "ecs": 1450.2914354053546, + "pef": 919.7149683050168 } }, { @@ -80,27 +80,27 @@ "countryMaking=FR" ], "impacts": { - "acd": 0.05726100108538642, - "cch": 11.430678438438557, - "etf": 15.277459947025957, - "etf-c": 34.84265300567465, - "fru": 206.01535100674178, - "fwe": 0.0011795776587754671, - "htc": 1.154092004935306e-9, - "htc-c": 9.46355705568073e-10, - "htn": 1.6726140174842664e-8, - "htn-c": 8.465101714748085e-9, - "ior": 13.251490281828953, - "ldu": 21.306122733566685, - "mru": 0.00007180961970512356, - "ozd": 0.000051345605385998686, - "pco": 0.03355570801711961, - "pma": 5.336713519599066e-7, - "swe": 0.011700116042033405, - "tre": 0.07875433537173472, - "wtu": 1.164646515699395, - "ecs": 1617.374306804295, - "pef": 1143.2791117460363 + "acd": 0.057581557642514186, + "cch": 11.486772334324144, + "etf": 15.634225651652217, + "etf-c": 35.30084273467208, + "fru": 206.83621120280137, + "fwe": 0.0011841875998579946, + "htc": 1.1733390915883777e-9, + "htc-c": 9.657327058055596e-10, + "htn": 1.7104907125727847e-8, + "htn-c": 8.594192534789848e-9, + "ior": 13.251972542033904, + "ldu": 21.656481609935792, + "mru": 0.00007192741894281859, + "ozd": 0.00005134618451117797, + "pco": 0.03386786925642574, + "pma": 5.381982335152771e-7, + "swe": 0.011781122757516282, + "tre": 0.07959215390253874, + "wtu": 1.1726385765499685, + "ecs": 1622.3567024833867, + "pef": 1148.1148870018326 } }, { @@ -116,27 +116,27 @@ "countryMaking=FR" ], "impacts": { - "acd": 0.03136758199562056, - "cch": 5.73617958103508, - "etf": 73.59235960432358, - "etf-c": 150.75526890312173, - "fru": 115.00637146175967, - "fwe": 0.0012020066613822507, - "htc": 9.285876200879071e-10, - "htc-c": 1.4260328317647246e-9, - "htn": 1.6450048443954115e-8, - "htn-c": 1.8946423613892236e-8, - "ior": 13.307040510287745, - "ldu": 60.43084658264571, - "mru": 0.000051638439899460184, - "ozd": 0.00003306021378271851, - "pco": 0.014406513880314486, - "pma": 2.9841384996321926e-7, - "swe": 0.015247390003884994, - "tre": 0.0802112463977537, - "wtu": 6.938926153229093, - "ecs": 1264.253322580562, - "pef": 808.6650530625744 + "acd": 0.03152481557850885, + "cch": 5.7623391873436365, + "etf": 74.02203123770292, + "etf-c": 151.43828158178619, + "fru": 115.3694352814857, + "fwe": 0.0012059961939220561, + "htc": 9.408805844144814e-10, + "htc-c": 1.4406978925029883e-9, + "htn": 1.670283464363093e-8, + "htn-c": 1.906926773536258e-8, + "ior": 13.307510680679485, + "ldu": 60.793578837279895, + "mru": 0.00005173472732919651, + "ozd": 0.000033069825250950026, + "pco": 0.014561425864626854, + "pma": 3.008625307764535e-7, + "swe": 0.015320376929918441, + "tre": 0.08079622398774226, + "wtu": 6.9639123932412055, + "ecs": 1267.9101496135304, + "pef": 811.4356101328748 } }, { @@ -151,27 +151,27 @@ "disabledSteps=ennobling" ], "impacts": { - "acd": 0.03285061747828521, - "cch": 3.467462533557159, - "etf": 131.19165884805642, - "etf-c": 251.917120164505, - "fru": 83.19386944418443, - "fwe": 0.0012812928247882068, - "htc": 6.877700085487217e-10, - "htc-c": 1.2672109600073182e-9, - "htn": 1.772755177044944e-8, - "htn-c": 3.327076914892387e-8, - "ior": 12.844016464249076, - "ldu": 100.36714158360871, - "mru": 0.000015799120970367426, - "ozd": 1.771166191843897e-7, - "pco": 0.013530668136250815, - "pma": 2.8119502121058356e-7, - "swe": 0.02256153447477876, - "tre": 0.11731038150597263, - "wtu": 13.893385126239512, - "ecs": 1211.9684170584155, - "pef": 711.1273507916327 + "acd": 0.03304139570903395, + "cch": 3.4946630396619085, + "etf": 131.8206093278565, + "etf-c": 253.00644259358452, + "fru": 83.53499556118432, + "fwe": 0.0012865964018241127, + "htc": 7.000351294941802e-10, + "htc-c": 1.2821805198555586e-9, + "htn": 1.8001092896262975e-8, + "htn-c": 3.344391071571918e-8, + "ior": 12.844507622401041, + "ldu": 100.87996147118199, + "mru": 0.000015896873941184877, + "ozd": 1.7777448468287237e-7, + "pco": 0.013694195018363142, + "pma": 2.838567035748975e-7, + "swe": 0.022668187659341657, + "tre": 0.11804694713872195, + "wtu": 13.941791629352936, + "ecs": 1216.7933769403794, + "pef": 714.3342887484364 } }, { @@ -188,27 +188,27 @@ "airTransportRatio=1" ], "impacts": { - "acd": 0.17722447021175042, - "cch": 29.220539830763823, - "etf": 398.0046362304084, - "etf-c": 927.7108872905228, - "fru": 305.7735885010402, - "fwe": 0.005856123911524636, - "htc": 4.293770543739464e-9, - "htc-c": 6.2789577774944456e-9, - "htn": 1.2409315049478433e-7, - "htn-c": 9.004875552369704e-8, - "ior": 16.32624483164245, - "ldu": 274.3683670172822, - "mru": 0.00014282364800809327, - "ozd": 0.00008057387845328036, - "pco": 0.09732511847581189, - "pma": 0.000001964238741109258, - "swe": 0.07968554841428832, - "tre": 0.5001375997947459, - "wtu": 35.02468574572376, - "ecs": 5024.788688895064, - "pef": 3043.3311917520828 + "acd": 0.17776530284519634, + "cch": 29.29713112389512, + "etf": 400.0061382866833, + "etf-c": 931.1534694580287, + "fru": 306.7250384898656, + "fwe": 0.005872724272504519, + "htc": 4.330174862471458e-9, + "htc-c": 6.325543794413083e-9, + "htn": 1.2485534139249358e-7, + "htn-c": 9.056708643635269e-8, + "ior": 16.32772384454294, + "ldu": 275.870695207961, + "mru": 0.00014311958620352283, + "ozd": 0.00008057582979176627, + "pco": 0.09775567050471355, + "pma": 0.0000019718066464557678, + "swe": 0.08000116470598892, + "tre": 0.5022110547143145, + "wtu": 35.180613817523366, + "ecs": 4811.167813476992, + "pef": 3052.638092437028 } }, { @@ -224,27 +224,27 @@ "fading=true" ], "impacts": { - "acd": 0.19765495767367622, - "cch": 27.714300828756784, - "etf": 391.55199166555997, - "etf-c": 984.5710735849585, - "fru": 283.18635414809796, - "fwe": 0.013010486351602946, - "htc": 4.166350461842815e-9, - "htc-c": 6.058019344719065e-9, - "htn": 1.0808654876338244e-7, - "htn-c": 9.655709496179501e-8, - "ior": 16.250956157882147, - "ldu": 314.33702358990746, - "mru": 0.00014643102143022628, - "ozd": 0.00008275031953862176, - "pco": 0.08218189933890796, - "pma": 0.0000019579238422117716, - "swe": 0.0865174249238061, - "tre": 0.4693534112163854, - "wtu": 40.98087480750406, - "ecs": 4965.610570161154, - "pef": 3154.4908547939185 + "acd": 0.19650754361129413, + "cch": 27.34278194470025, + "etf": 390.8126880668444, + "etf-c": 985.4292861961296, + "fru": 278.1848505021621, + "fwe": 0.013024377908051372, + "htc": 4.162600787641573e-9, + "htc-c": 6.082474936122221e-9, + "htn": 1.0400791489444354e-7, + "htn-c": 9.70449168665708e-8, + "ior": 16.251032132818466, + "ldu": 315.86133436995925, + "mru": 0.00014671716699291178, + "ozd": 0.00008274534837021724, + "pco": 0.08009650824743483, + "pma": 0.0000019636491591381196, + "swe": 0.08611009389584703, + "tre": 0.4635144069953214, + "wtu": 41.14890419887185, + "ecs": 4949.858297610277, + "pef": 3134.1630944264157 } }, { @@ -260,27 +260,27 @@ "fading=false" ], "impacts": { - "acd": 0.16919839778952084, - "cch": 23.777660767811263, - "etf": 382.96933902601796, - "etf-c": 975.3936690299714, - "fru": 245.73287357468237, - "fwe": 0.011027564651083278, - "htc": 3.2119930442141052e-9, - "htc-c": 4.9521349275326085e-9, - "htn": 8.562723921230658e-8, - "htn-c": 9.505431109586212e-8, - "ior": 16.22469041121089, - "ldu": 295.0145579973466, - "mru": 0.00014397650264865014, - "ozd": 0.00008270664080826244, - "pco": 0.07011875405995191, - "pma": 0.0000015299021421205005, - "swe": 0.07993753303348655, - "tre": 0.43009685281144183, - "wtu": 40.61586633690124, - "ecs": 4662.742937425237, - "pef": 2818.6043491275173 + "acd": 0.16805098372713878, + "cch": 23.406141883754728, + "etf": 382.2300354273023, + "etf-c": 976.2518816411425, + "fru": 240.7313699287465, + "fwe": 0.011041456207531702, + "htc": 3.2082433700128635e-9, + "htc-c": 4.9765905189357655e-9, + "htn": 8.154860534336768e-8, + "htn-c": 9.554213300063791e-8, + "ior": 16.224766386147213, + "ldu": 296.5388687773984, + "mru": 0.00014426264821133564, + "ozd": 0.00008270166963985792, + "pco": 0.06803336296847878, + "pma": 0.0000015356274590468485, + "swe": 0.07953020200552749, + "tre": 0.42425784859037785, + "wtu": 40.78389572826903, + "ecs": 4646.99066487436, + "pef": 2798.276588760015 } }, { @@ -296,27 +296,27 @@ "reparability=1.15" ], "impacts": { - "acd": 0.0414446685749946, - "cch": 6.048874393344061, - "etf": 133.45735497327883, - "etf-c": 281.03257869705175, - "fru": 108.41610613408756, - "fwe": 0.0015967563755970645, - "htc": 9.202232406694513e-10, - "htc-c": 1.5167242473017484e-9, - "htn": 2.2684998010224684e-8, - "htn-c": 3.378964481489889e-8, - "ior": 13.313345405119325, - "ldu": 105.51839908615344, - "mru": 0.000052078700426912154, - "ozd": 0.000030370478290087493, - "pco": 0.016994499640779308, - "pma": 3.6240125804991626e-7, - "swe": 0.02536100942287826, - "tre": 0.12574953428356123, - "wtu": 13.97456474348671, - "ecs": 1469.1531378179673, - "pef": 939.757440532827 + "acd": 0.04163544680574335, + "cch": 6.076074899448809, + "etf": 134.0863054530789, + "etf-c": 282.1219011261312, + "fru": 108.75723225108744, + "fwe": 0.0016020599526329704, + "htc": 9.324883616149098e-10, + "htc-c": 1.5316938071499886e-9, + "htn": 2.2958539136038225e-8, + "htn-c": 3.396278638169421e-8, + "ior": 13.313836563271293, + "ldu": 106.0312189737267, + "mru": 0.000052176453397729595, + "ozd": 0.00003037113615558597, + "pco": 0.017158026522891636, + "pma": 3.650629404142302e-7, + "swe": 0.025467662607441162, + "tre": 0.12648609991631055, + "wtu": 14.022971246600134, + "ecs": 1473.9780976999311, + "pef": 942.9643784896306 } }, { @@ -331,27 +331,27 @@ "makingWaste=0" ], "impacts": { - "acd": 0.03556277405549819, - "cch": 5.274011521794696, - "etf": 113.66496753834603, - "etf-c": 239.18693472401281, - "fru": 99.89354028951128, - "fwe": 0.001379642414386105, - "htc": 7.975127330222743e-10, - "htc-c": 1.3184705161562507e-9, - "htn": 1.934041722376756e-8, - "htn-c": 2.878076729242554e-8, - "ior": 13.16583070424621, - "ldu": 91.14088216514263, - "mru": 0.00004479823453750349, - "ozd": 0.000025826455410885885, - "pco": 0.01467685700279186, - "pma": 3.1336217874581686e-7, - "swe": 0.02167050815279059, - "tre": 0.10759516484280496, - "wtu": 11.891846278042784, - "ecs": 1299.6317469908283, - "pef": 837.4947256106926 + "acd": 0.03573846577551521, + "cch": 5.29996737386433, + "etf": 114.22944021552313, + "etf-c": 240.15186918362068, + "fru": 100.2239535034761, + "fwe": 0.0013843883280009535, + "htc": 8.095023395572573e-10, + "htc-c": 1.3329320985332255e-9, + "htn": 1.9605468225485186e-8, + "htn-c": 2.8937573933380658e-8, + "ior": 13.166286970554003, + "ldu": 91.60870785555007, + "mru": 0.00004489017344329534, + "ozd": 0.000025827064835365563, + "pco": 0.014834561195870921, + "pma": 3.159043475614539e-7, + "swe": 0.02176633947907204, + "tre": 0.10827607026526874, + "wtu": 11.933371318307108, + "ecs": 1304.0314709229826, + "pef": 840.4876551108515 } }, { @@ -366,27 +366,27 @@ "makingWaste=0.25" ], "impacts": { - "acd": 0.046673019258991415, - "cch": 6.737641390276829, - "etf": 151.05058824877455, - "etf-c": 318.2287066730863, - "fru": 115.99172021815534, - "fwe": 0.0017897465633401396, - "htc": 1.029299247466942e-9, - "htc-c": 1.6929497860977452e-9, - "htn": 2.5657958709297684e-8, - "htn-c": 3.824198039043076e-8, - "ior": 13.444469583673206, - "ldu": 118.29841412705193, - "mru": 0.00005855022566194208, - "ozd": 0.00003440960973826669, - "pco": 0.019054626430101487, - "pma": 4.059915507646712e-7, - "swe": 0.028641454996289513, - "tre": 0.1418867515642335, - "wtu": 15.825870046103532, - "ecs": 1619.8388185532008, - "pef": 1030.657631574724 + "acd": 0.04687720772150169, + "cch": 6.765948255523903, + "etf": 151.73685233090626, + "etf-c": 319.4285961861407, + "fru": 116.34236891563084, + "fwe": 0.001795545841194763, + "htc": 1.041809270110601e-9, + "htc-c": 1.7083708814760004e-9, + "htn": 2.593904661208537e-8, + "htn-c": 3.842964189130624e-8, + "ior": 13.444991756797775, + "ldu": 118.85122885655039, + "mru": 0.000058653146690560065, + "ozd": 0.00003441031066244854, + "pco": 0.019223329035798944, + "pma": 4.087594673944759e-7, + "swe": 0.028757727610435928, + "tre": 0.14267279293945884, + "wtu": 15.880393405082835, + "ecs": 1625.0417659461073, + "pef": 1034.0547992707675 } }, { @@ -402,27 +402,27 @@ "surfaceMass=300" ], "impacts": { - "acd": 0.07267634397534148, - "cch": 10.566370145958176, - "etf": 235.05735479215312, - "etf-c": 495.31278142778234, - "fru": 175.24855963490427, - "fwe": 0.002796540073534118, - "htc": 1.5919968071366613e-9, - "htc-c": 2.6127190836195204e-9, - "htn": 3.997288100353046e-8, - "htn-c": 5.951291773435012e-8, - "ior": 22.763840053174576, - "ldu": 185.72468717008135, - "mru": 0.00009102947573620496, - "ozd": 0.00005359060692221132, - "pco": 0.029737143509036355, - "pma": 6.332899101003791e-7, - "swe": 0.04461787851377972, - "tre": 0.22089877023652157, - "wtu": 24.630629375675866, - "ecs": 2562.0424840947135, - "pef": 1622.08056836771 + "acd": 0.07301301144136867, + "cch": 10.614371039084205, + "etf": 236.16726740356503, + "etf-c": 497.23511512615795, + "fru": 175.8505469001982, + "fwe": 0.0028058993271268926, + "htc": 1.6136411382168829e-9, + "htc-c": 2.6391359539399452e-9, + "htn": 4.0455600637319064e-8, + "htn-c": 5.98184616757536e-8, + "ior": 22.76470680285452, + "ldu": 186.62966344226942, + "mru": 0.00009120198097882398, + "ozd": 0.00005359176786132629, + "pco": 0.03002572035982282, + "pma": 6.379869966256389e-7, + "swe": 0.04480609001594954, + "tre": 0.22219859194137329, + "wtu": 24.71605261646426, + "ecs": 2570.5571191805325, + "pef": 1627.7398706444221 } }, { @@ -438,27 +438,27 @@ "printing=pigment" ], "impacts": { - "acd": 0.07394588489266214, - "cch": 10.83602593808956, - "etf": 235.61657846444567, - "etf-c": 507.65662917666367, - "fru": 184.38310752368184, - "fwe": 0.002896826121073032, - "htc": 1.6498750628912446e-9, - "htc-c": 2.681280684984667e-9, - "htn": 4.10706733454279e-8, - "htn-c": 5.987239547781978e-8, - "ior": 23.04193808555303, - "ldu": 186.97988567823194, - "mru": 0.00009158624796893748, - "ozd": 0.00005360116790855931, - "pco": 0.030507001264879974, - "pma": 6.483034674210423e-7, - "swe": 0.0456672457563599, - "tre": 0.22289894843837044, - "wtu": 24.6477403557429, - "ecs": 2615.2682471286835, - "pef": 1654.264703386901 + "acd": 0.07428255235868933, + "cch": 10.884026831215587, + "etf": 236.72649107585758, + "etf-c": 509.5789628750392, + "fru": 184.98509478897574, + "fwe": 0.0029061853746658066, + "htc": 1.671519393971466e-9, + "htc-c": 2.707697555305092e-9, + "htn": 4.15533929792165e-8, + "htn-c": 6.017793941922328e-8, + "ior": 23.04280483523297, + "ldu": 187.88486195042003, + "mru": 0.0000917587532115565, + "ozd": 0.000053602328847674277, + "pco": 0.030795578115666435, + "pma": 6.53000553946302e-7, + "swe": 0.04585545725852972, + "tre": 0.22419877014322215, + "wtu": 24.7331635965313, + "ecs": 2623.782882214503, + "pef": 1659.9240056636133 } }, { @@ -474,27 +474,27 @@ "printing=substantive" ], "impacts": { - "acd": 0.07446448136980308, - "cch": 10.865459053092978, - "etf": 235.71998929884572, - "etf-c": 500.6037735496913, - "fru": 185.257732233438, - "fwe": 0.002922149679109287, - "htc": 1.6609112981200832e-9, - "htc-c": 2.69366950581472e-9, - "htn": 4.1291958055739324e-8, - "htn-c": 5.968008004359814e-8, - "ior": 23.082521039967514, - "ldu": 187.14773137416543, - "mru": 0.00009181908475489009, - "ozd": 0.000053599886340053184, - "pco": 0.030583985163537, - "pma": 6.516506209819572e-7, - "swe": 0.045725416514139174, - "tre": 0.22317352611139882, - "wtu": 24.650544818224496, - "ecs": 2603.8607504048723, - "pef": 1658.8381379248804 + "acd": 0.07480114883583026, + "cch": 10.913459946219007, + "etf": 236.82990191025763, + "etf-c": 502.52610724806686, + "fru": 185.8597194987319, + "fwe": 0.0029315089327020608, + "htc": 1.6825556292003047e-9, + "htc-c": 2.720086376135145e-9, + "htn": 4.177467768952792e-8, + "htn-c": 5.998562398500163e-8, + "ior": 23.083387789647457, + "ldu": 188.0527076463535, + "mru": 0.00009199158999750911, + "ozd": 0.000053601047279168154, + "pco": 0.030872562014323462, + "pma": 6.563477075072172e-7, + "swe": 0.045913628016308994, + "tre": 0.22447334781625053, + "wtu": 24.735968059012894, + "ecs": 2612.3753854906913, + "pef": 1664.4974402015926 } }, { @@ -510,27 +510,27 @@ "printing=pigment;0.5" ], "impacts": { - "acd": 0.07565313052469849, - "cch": 11.182825403800452, - "etf": 236.45541397288443, - "etf-c": 526.1724007999856, - "fru": 196.82181516999697, - "fwe": 0.002983897665138724, - "htc": 1.73669244652312e-9, - "htc-c": 2.784123087032387e-9, - "htn": 4.271736185827405e-8, - "htn-c": 6.04116120930243e-8, - "ior": 23.458213102307457, - "ldu": 188.58627379319242, - "mru": 0.00009215383538378021, - "ozd": 0.000053609923385912195, - "pco": 0.0314264920250721, - "pma": 6.670176721207272e-7, - "swe": 0.04594982938891271, - "tre": 0.2256601688992288, - "wtu": 24.67340682584345, - "ecs": 2688.6163069018926, - "pef": 1694.7748529334613 + "acd": 0.07598979799072568, + "cch": 11.230826296926482, + "etf": 237.56532658429637, + "etf-c": 528.0947344983612, + "fru": 197.42380243529087, + "fwe": 0.002993256918731499, + "htc": 1.7583367776033414e-9, + "htc-c": 2.810539957352812e-9, + "htn": 4.3200081492062654e-8, + "htn-c": 6.071715603442778e-8, + "ior": 23.4590798519874, + "ldu": 189.4912500653805, + "mru": 0.00009232634062639925, + "ozd": 0.000053611084325027164, + "pco": 0.03171506887585856, + "pma": 6.71714758645987e-7, + "swe": 0.046138040891082525, + "tre": 0.2269599906040805, + "wtu": 24.758830066631848, + "ecs": 2697.130941987712, + "pef": 1700.4341552101737 } }, { @@ -545,27 +545,27 @@ "upcycled=true" ], "impacts": { - "acd": 0.0028783103726423317, - "cch": 1.034447974270687, - "etf": 2.148056729627463, - "etf-c": 2.9423659495513848, - "fru": 74.16903603999697, - "fwe": 0.0001791955931726597, - "htc": 1.4699590611006234e-10, - "htc-c": 2.8471813917534933e-10, - "htn": 4.71319645111522e-10, - "htn-c": 5.598702944994033e-10, - "ior": 13.355660450532385, - "ldu": 10.346336411626782, - "mru": 0.000004769965172754928, - "ozd": 8.310895092724175e-8, - "pco": 0.00189779033199731, - "pma": 4.4226576928059735e-8, - "swe": 0.0009493384564102386, - "tre": 0.006135195458220896, - "wtu": 0.1324275226503942, - "ecs": 318.5020583098391, - "pef": 308.9809122920371 + "acd": 0.002968511865179645, + "cch": 1.0533507868080005, + "etf": 2.3471551919408955, + "etf-c": 3.202435248820042, + "fru": 74.4387428034298, + "fwe": 0.0001807814140681821, + "htc": 1.5742426431901755e-10, + "htc-c": 2.963011825484837e-10, + "htn": 6.882599436189848e-10, + "htn-c": 6.241123556934332e-10, + "ior": 13.35591899638985, + "ldu": 10.55919498476111, + "mru": 0.00000482895771006836, + "ozd": 8.344387630037608e-8, + "pco": 0.0020224992872211905, + "pma": 4.609150230119407e-8, + "swe": 0.000983845919096806, + "tre": 0.006500693022400001, + "wtu": 0.13495760676979718, + "ecs": 320.49211185973485, + "pef": 310.761127204542 } } ] diff --git a/tests/server.spec.js b/tests/server.spec.js index 677ee0303..4246f5a7b 100644 --- a/tests/server.spec.js +++ b/tests/server.spec.js @@ -296,11 +296,6 @@ describe("API", () => { airTransportRatio: 0.5, durability: 1.2, reparability: 1.2, - makingWaste: null, - makingComplexity: null, - yarnSize: null, - surfaceMass: null, - knittingProcess: null, disabledSteps: ["use"], }); expectStatus(response, 200);