diff --git a/.env.sample b/.env.sample index 469807bdd..da250d0cb 100644 --- a/.env.sample +++ b/.env.sample @@ -11,6 +11,6 @@ ENABLE_FOOD_SECTION=True MATOMO_HOST=stats.beta.gouv.fr MATOMO_SITE_ID=57 MATOMO_TOKEN=xxx -NODE_ENV=production +NODE_ENV=development SCALINGO_POSTGRESQL_URL=please-change-this SENTRY_DSN=please-change-this diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 265b0b078..db35ece7c 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: node-version: [20.x] - python-version: [3.11] + python-version: [3.12] steps: - uses: actions/checkout@v4 @@ -53,15 +53,13 @@ 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 - name: Install Python dependencies - run: pip install pipenv && pipenv install + run: pip install pipenv && pipenv install -d - name: Install Ubuntu dependencies run: | @@ -91,9 +89,6 @@ jobs: - name: Build app run: npm run build --if-present - - name: Build Elm static Db - run: npm run db:build - - name: Run prettier, openapi & ruff formatting check run: npm run lint:all diff --git a/.github/workflows/score_history.yml b/.github/workflows/score_history.yml index 44a174214..486188c1e 100644 --- a/.github/workflows/score_history.yml +++ b/.github/workflows/score_history.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: node-version: [20.x] - python-version: [3.11] + python-version: [3.12] # Run the job manually, on push to master or if the commit contains "[score_history]" if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/master' || contains(github.event.head_commit.message, '[score_history]') }} @@ -65,7 +65,7 @@ jobs: run: npm ci --prefer-offline --no-audit - name: Install Python dependencies - run: pip install pipenv && pipenv install + run: pip install pipenv && pipenv install -d - name: Install Ubuntu dependencies diff --git a/.gitignore b/.gitignore index c958f2513..855912abb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.env /dist elm-stuff +/check-db-app.js /compute-aggregated-app.js /node_modules /public/app.js diff --git a/.slugignore b/.slugignore new file mode 100644 index 000000000..c09e1e80b --- /dev/null +++ b/.slugignore @@ -0,0 +1,12 @@ +elm-stuff/ +data/ +.elm/ +src/ +ecobalyse-private/.git +tests/ +styles.scss +review/ + +node_modules/@parcel +node_modules/@swc +node_modules/@elm_binaries diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa8e7962..da4586ff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [2.2.0](https://github.com/MTES-MCT/ecobalyse/compare/v2.1.1...v2.2.0) (2024-09-12) + + +### Features + +* add app version to openapi docs in the root endpoint. ([#726](https://github.com/MTES-MCT/ecobalyse/issues/726)) ([5959c34](https://github.com/MTES-MCT/ecobalyse/commit/5959c3483600a2668390f6f0dd8a2778218436c0)) +* add holistic durability in exploratory mode ([#721](https://github.com/MTES-MCT/ecobalyse/issues/721)) ([774faf3](https://github.com/MTES-MCT/ecobalyse/commit/774faf3ad553687e154d4f46bf1227ccd0571710)) +* render app version details in the changelog. ([#725](https://github.com/MTES-MCT/ecobalyse/issues/725)) ([8f6ea50](https://github.com/MTES-MCT/ecobalyse/commit/8f6ea50aa1d11c37a0afc54e0de68a69cffbb1bb)) + + +### Bug Fixes + +* accept custom making complexity for upcycled garments. ([#723](https://github.com/MTES-MCT/ecobalyse/issues/723)) ([8f61547](https://github.com/MTES-MCT/ecobalyse/commit/8f61547f942b3fefd2129550125e9e7c0591cbaa)) +* **ci:** check for ecobalyse-private when extracting the branch name ([#733](https://github.com/MTES-MCT/ecobalyse/issues/733)) ([23ae8a5](https://github.com/MTES-MCT/ecobalyse/commit/23ae8a564854ab6cdb4f38bc62f04a04a47ca3c4)) +* don't add disabled step impacts to lifecycle totals. ([#719](https://github.com/MTES-MCT/ecobalyse/issues/719)) ([b6a7e1c](https://github.com/MTES-MCT/ecobalyse/commit/b6a7e1c4ff190acef8d0af0ee0f02b93b19ee32d)) +* ensure express app is properly monitored by Sentry. ([#729](https://github.com/MTES-MCT/ecobalyse/issues/729)) ([84a39aa](https://github.com/MTES-MCT/ecobalyse/commit/84a39aa69a8771294195787401cd0e9e11403d1f)) +* make scalingo not segfaulting. ([#728](https://github.com/MTES-MCT/ecobalyse/issues/728)) ([1de5140](https://github.com/MTES-MCT/ecobalyse/commit/1de5140c7e75bae20bd6afb58a0180d389dd3254)) +* use fabric processes to compute fabric waste ([#712](https://github.com/MTES-MCT/ecobalyse/issues/712)) ([1cce55b](https://github.com/MTES-MCT/ecobalyse/commit/1cce55b229cc9e14de72037d381d06f2255fe0bd)) + ## [2.1.1](https://github.com/MTES-MCT/ecobalyse/compare/v2.1.0...v2.1.1) (2024-09-02) diff --git a/Pipfile b/Pipfile index 4867d049c..9591cd542 100644 --- a/Pipfile +++ b/Pipfile @@ -3,18 +3,16 @@ url = "https://pypi.org/simple" verify_ssl = true name = "pypi" +[requires] +python_version = "3.12" + [packages] django = ">=5.0.8,<6" django-mail-auth = ">=3.2,<3.3" gunicorn = ">=22,<23" psycopg2-binary = "2.9.9" python-decouple = ">=3,<4" -ruff = "*" -SQLAlchemy = "2.0.30" -pandas = "1.5.3" GitPython = "3.1.43" -pre-commit = "*" -numpy = ">=1,<2" pytest-django = "*" pygithub = "*" ecobalyse = {file = "packages/python/ecobalyse"} @@ -22,3 +20,8 @@ typer = "*" pytest-mock = "*" [dev-packages] +numpy = ">=1,<2" +pre-commit = "*" +pandas = "1.5.3" +ruff = "*" +SQLAlchemy = "2.0.30" diff --git a/Pipfile.lock b/Pipfile.lock index 5b225a94a..04a3517f2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,10 +1,12 @@ { "_meta": { "hash": { - "sha256": "9fc750a355fe9b80eda270865ec2d3366737413d689c16781ff0739e6aa7661d" + "sha256": "94f9971a86b6cb8853c5c8be6433eefe477315fc07bd0132d62c3c1d7fadbbe8" }, "pipfile-spec": 6, - "requires": {}, + "requires": { + "python_version": "3.12" + }, "sources": [ { "name": "pypi", @@ -24,92 +26,84 @@ }, "certifi": { "hashes": [ - "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", - "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" ], "markers": "python_version >= '3.6'", - "version": "==2024.7.4" + "version": "==2024.8.30" }, "cffi": { "hashes": [ - "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f", - "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab", - "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499", - "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058", - "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693", - "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb", - "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377", - "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885", - "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2", - "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401", - "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4", - "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b", - "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59", - "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f", - "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c", - "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555", - "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa", - "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424", - "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb", - "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2", - "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8", - "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e", - "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9", - "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82", - "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828", - "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759", - "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc", - "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118", - "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf", - "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932", - "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a", - "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29", - "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206", - "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2", - "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c", - "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c", - "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0", - "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a", - "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195", - "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6", - "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9", - "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc", - "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb", - "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0", - "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7", - "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb", - "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a", - "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492", - "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720", - "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42", - "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7", - "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d", - "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d", - "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb", - "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4", - "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2", - "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b", - "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8", - "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e", - "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204", - "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3", - "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150", - "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4", - "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76", - "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e", - "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb", - "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91" - ], - "markers": "platform_python_implementation != 'PyPy'", - "version": "==1.17.0" - }, - "cfgv": { - "hashes": [ - "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", - "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" + "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", + "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", + "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", + "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", + "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", + "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", + "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", + "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", + "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", + "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", + "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", + "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", + "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", + "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", + "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", + "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", + "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", + "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", + "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", + "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", + "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", + "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", + "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", + "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", + "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", + "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", + "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", + "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", + "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", + "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", + "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", + "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", + "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", + "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", + "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", + "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", + "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", + "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", + "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", + "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", + "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", + "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", + "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", + "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", + "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", + "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", + "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", + "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", + "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", + "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", + "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", + "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", + "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", + "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", + "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", + "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", + "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", + "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", + "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", + "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", + "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", + "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", + "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", + "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", + "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", + "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", + "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" ], "markers": "python_version >= '3.8'", - "version": "==3.4.0" + "version": "==1.17.1" }, "charset-normalizer": { "hashes": [ @@ -245,7 +239,6 @@ "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289" ], - "index": "pypi", "markers": "python_version >= '3.7'", "version": "==43.0.1" }, @@ -257,21 +250,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.2.14" }, - "distlib": { - "hashes": [ - "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", - "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64" - ], - "version": "==0.3.8" - }, "django": { "hashes": [ - "sha256:848a5980e8efb76eea70872fb0e4bc5e371619c70fffbe48e3e1b50b2c09455d", - "sha256:d3b811bf5371a26def053d7ee42a9df1267ef7622323fe70a601936725aa4557" + "sha256:021ffb7fdab3d2d388bc8c7c2434eb9c1f6f4d09e6119010bbb1694dda286bc2", + "sha256:71603f27dac22a6533fb38d83072eea9ddb4017fead6f67f2562a40402d61c3f" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==5.1" + "version": "==5.1.1" }, "django-mail-auth": { "hashes": [ @@ -285,14 +271,6 @@ "ecobalyse": { "file": "packages/python/ecobalyse" }, - "filelock": { - "hashes": [ - "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb", - "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7" - ], - "markers": "python_version >= '3.8'", - "version": "==3.15.4" - }, "gitdb": { "hashes": [ "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4", @@ -310,70 +288,6 @@ "markers": "python_version >= '3.7'", "version": "==3.1.43" }, - "greenlet": { - "hashes": [ - "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67", - "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6", - "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257", - "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4", - "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676", - "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61", - "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc", - "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca", - "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7", - "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728", - "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305", - "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6", - "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379", - "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414", - "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04", - "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a", - "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf", - "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491", - "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559", - "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e", - "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274", - "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb", - "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b", - "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9", - "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b", - "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be", - "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506", - "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405", - "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113", - "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f", - "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5", - "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230", - "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d", - "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f", - "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a", - "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e", - "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61", - "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6", - "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d", - "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71", - "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22", - "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2", - "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3", - "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067", - "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc", - "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881", - "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3", - "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e", - "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac", - "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53", - "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0", - "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b", - "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83", - "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41", - "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c", - "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf", - "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da", - "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" - ], - "markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", - "version": "==3.0.3" - }, "gunicorn": { "hashes": [ "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9", @@ -383,21 +297,13 @@ "markers": "python_version >= '3.7'", "version": "==22.0.0" }, - "identify": { - "hashes": [ - "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf", - "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0" - ], - "markers": "python_version >= '3.8'", - "version": "==2.6.0" - }, "idna": { "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" ], - "markers": "python_version >= '3.5'", - "version": "==3.7" + "markers": "python_version >= '3.6'", + "version": "==3.8" }, "iniconfig": { "hashes": [ @@ -423,57 +329,6 @@ "markers": "python_version >= '3.7'", "version": "==0.1.2" }, - "nodeenv": { - "hashes": [ - "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", - "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==1.9.1" - }, - "numpy": { - "hashes": [ - "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", - "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", - "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", - "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", - "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", - "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", - "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea", - "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c", - "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", - "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", - "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be", - "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", - "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", - "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", - "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", - "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd", - "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c", - "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", - "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0", - "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c", - "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", - "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", - "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", - "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6", - "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", - "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", - "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30", - "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", - "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", - "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", - "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", - "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", - "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764", - "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", - "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", - "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.26.4" - }, "packaging": { "hashes": [ "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", @@ -482,48 +337,6 @@ "markers": "python_version >= '3.8'", "version": "==24.1" }, - "pandas": { - "hashes": [ - "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813", - "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792", - "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406", - "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373", - "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328", - "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996", - "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf", - "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6", - "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7", - "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc", - "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1", - "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23", - "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a", - "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51", - "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572", - "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31", - "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5", - "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a", - "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003", - "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d", - "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354", - "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee", - "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa", - "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0", - "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9", - "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae", - "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.5.3" - }, - "platformdirs": { - "hashes": [ - "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", - "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" - ], - "markers": "python_version >= '3.8'", - "version": "==4.2.2" - }, "pluggy": { "hashes": [ "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", @@ -532,15 +345,6 @@ "markers": "python_version >= '3.8'", "version": "==1.5.0" }, - "pre-commit": { - "hashes": [ - "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", - "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==3.8.0" - }, "psycopg2-binary": { "hashes": [ "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9", @@ -630,12 +434,12 @@ }, "pygithub": { "hashes": [ - "sha256:0148d7347a1cdeed99af905077010aef81a4dad988b0ba51d4108bf66b443f7e", - "sha256:65b499728be3ce7b0cd2cd760da3b32f0f4d7bc55e5e0677617f90f6564e793e" + "sha256:6601e22627e87bac192f1e2e39c6e6f69a43152cfb8f307cee575879320b3051", + "sha256:81935aa4bdc939fba98fee1cb47422c09157c56a27966476ff92775602b9ee24" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.3.0" + "markers": "python_version >= '3.8'", + "version": "==2.4.0" }, "pygments": { "hashes": [ @@ -674,20 +478,20 @@ }, "pytest": { "hashes": [ - "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", - "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce" + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" ], "markers": "python_version >= '3.8'", - "version": "==8.3.2" + "version": "==8.3.3" }, "pytest-django": { "hashes": [ - "sha256:5d054fe011c56f3b10f978f41a8efb2e5adfc7e680ef36fb571ada1f24779d90", - "sha256:ca1ddd1e0e4c227cf9e3e40a6afc6d106b3e70868fd2ac5798a22501271cd0c7" + "sha256:1d83692cb39188682dbb419ff0393867e9904094a549a7d38a3154d5731b2b99", + "sha256:8bf7bc358c9ae6f6fc51b6cebb190fe20212196e6807121f11bd6a3b03428314" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==4.8.0" + "version": "==4.9.0" }, "pytest-mock": { "hashes": [ @@ -698,14 +502,6 @@ "markers": "python_version >= '3.8'", "version": "==3.14.0" }, - "python-dateutil": { - "hashes": [ - "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", - "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.9.0.post0" - }, "python-decouple": { "hashes": [ "sha256:ba6e2657d4f376ecc46f77a3a615e058d93ba5e465c01bbe57289bfb7cce680f", @@ -714,72 +510,6 @@ "index": "pypi", "version": "==3.8" }, - "pytz": { - "hashes": [ - "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", - "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" - ], - "version": "==2024.1" - }, - "pyyaml": { - "hashes": [ - "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", - "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", - "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", - "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", - "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", - "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", - "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", - "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", - "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", - "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", - "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", - "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", - "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", - "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", - "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", - "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", - "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", - "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", - "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", - "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", - "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", - "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", - "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", - "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", - "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", - "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", - "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", - "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", - "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", - "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", - "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", - "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", - "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", - "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", - "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", - "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", - "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", - "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", - "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", - "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", - "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", - "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", - "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", - "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", - "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", - "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", - "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", - "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", - "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", - "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", - "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", - "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", - "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" - ], - "markers": "python_version >= '3.8'", - "version": "==6.0.2" - }, "requests": { "hashes": [ "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", @@ -790,36 +520,11 @@ }, "rich": { "hashes": [ - "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", - "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" + "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06", + "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a" ], "markers": "python_full_version >= '3.7.0'", - "version": "==13.7.1" - }, - "ruff": { - "hashes": [ - "sha256:2c7477c3b9da822e2db0b4e0b59e61b8a23e87886e727b327e7dcaf06213c5cf", - "sha256:392688dbb50fecf1bf7126731c90c11a9df1c3a4cdc3f481b53e851da5634fa5", - "sha256:3a0af7ab3f86e3dc9f157a928e08e26c4b40707d0612b01cd577cc84b8905cc9", - "sha256:3bc81074971b0ffad1bd0c52284b22411f02a11a012082a76ac6da153536e014", - "sha256:45efaae53b360c81043e311cdec8a7696420b3d3e8935202c2846e7a97d4edae", - "sha256:5278d3e095ccc8c30430bcc9bc550f778790acc211865520f3041910a28d0024", - "sha256:99d7ae0df47c62729d58765c593ea54c2546d5de213f2af2a19442d50a10cec9", - "sha256:9eb18dfd7b613eec000e3738b3f0e4398bf0153cb80bfa3e351b3c1c2f6d7b15", - "sha256:9fb4c4e8b83f19c9477a8745e56d2eeef07a7ff50b68a6998f7d9e2e3887bdc4", - "sha256:af3ffd8c6563acb8848d33cd19a69b9bfe943667f0419ca083f8ebe4224a3436", - "sha256:b2e0dd11e2ae553ee5c92a81731d88a9883af8db7408db47fc81887c1f8b672e", - "sha256:b4bb7de6a24169dc023f992718a9417380301b0c2da0fe85919f47264fb8add9", - "sha256:bc60c7d71b732c8fa73cf995efc0c836a2fd8b9810e115be8babb24ae87e0850", - "sha256:c2ebfc8f51ef4aca05dad4552bbcf6fe8d1f75b2f6af546cc47cc1c1ca916b5b", - "sha256:c62bc04c6723a81e25e71715aa59489f15034d69bf641df88cb38bdc32fd1dbb", - "sha256:d812615525a34ecfc07fd93f906ef5b93656be01dfae9a819e31caa6cfe758a1", - "sha256:faaa4060f4064c3b7aaaa27328080c932fa142786f8142aff095b42b6a2eb631", - "sha256:fe6d5f65d6f276ee7a0fc50a0cecaccb362d30ef98a110f99cac1c7872df2f18" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==0.6.1" + "version": "==13.8.1" }, "shellingham": { "hashes": [ @@ -829,14 +534,6 @@ "markers": "python_version >= '3.7'", "version": "==1.5.4" }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, "smmap": { "hashes": [ "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62", @@ -845,63 +542,7 @@ "markers": "python_version >= '3.7'", "version": "==5.0.1" }, - "sqlalchemy": { - "hashes": [ - "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7", - "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8", - "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb", - "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260", - "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0", - "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513", - "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b", - "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2", - "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3", - "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584", - "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255", - "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49", - "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7", - "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9", - "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af", - "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc", - "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e", - "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134", - "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd", - "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf", - "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c", - "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57", - "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa", - "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a", - "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90", - "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e", - "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6", - "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0", - "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb", - "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e", - "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221", - "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13", - "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7", - "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621", - "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a", - "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0", - "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e", - "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5", - "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5", - "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3", - "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797", - "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472", - "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b", - "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953", - "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9", - "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad", - "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46", - "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c", - "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.0.30" - }, - "sqlparse": { + "sqlparse": { "hashes": [ "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4", "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e" @@ -911,12 +552,12 @@ }, "typer": { "hashes": [ - "sha256:819aa03699f438397e876aa12b0d63766864ecba1b579092cc9fe35d886e34b6", - "sha256:c9c1613ed6a166162705b3347b8d10b661ccc5d95692654d0fb628118f2c34e6" + "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b", + "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.12.4" + "version": "==0.12.5" }, "typing-extensions": { "hashes": [ @@ -934,14 +575,6 @@ "markers": "python_version >= '3.8'", "version": "==2.2.2" }, - "virtualenv": { - "hashes": [ - "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a", - "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589" - ], - "markers": "python_version >= '3.7'", - "version": "==20.26.3" - }, "wrapt": { "hashes": [ "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc", @@ -1019,5 +652,390 @@ "version": "==1.16.0" } }, - "develop": {} + "develop": { + "cfgv": { + "hashes": [ + "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", + "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "distlib": { + "hashes": [ + "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", + "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64" + ], + "version": "==0.3.8" + }, + "filelock": { + "hashes": [ + "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec", + "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609" + ], + "markers": "python_version >= '3.8'", + "version": "==3.16.0" + }, + "greenlet": { + "hashes": [ + "sha256:01059afb9b178606b4b6e92c3e710ea1635597c3537e44da69f4531e111dd5e9", + "sha256:037d9ac99540ace9424cb9ea89f0accfaff4316f149520b4ae293eebc5bded17", + "sha256:0e49a65d25d7350cca2da15aac31b6f67a43d867448babf997fe83c7505f57bc", + "sha256:13ff8c8e54a10472ce3b2a2da007f915175192f18e6495bad50486e87c7f6637", + "sha256:1544b8dd090b494c55e60c4ff46e238be44fdc472d2589e943c241e0169bcea2", + "sha256:184258372ae9e1e9bddce6f187967f2e08ecd16906557c4320e3ba88a93438c3", + "sha256:1ddc7bcedeb47187be74208bc652d63d6b20cb24f4e596bd356092d8000da6d6", + "sha256:221169d31cada333a0c7fd087b957c8f431c1dba202c3a58cf5a3583ed973e9b", + "sha256:243a223c96a4246f8a30ea470c440fe9db1f5e444941ee3c3cd79df119b8eebf", + "sha256:24fc216ec7c8be9becba8b64a98a78f9cd057fd2dc75ae952ca94ed8a893bf27", + "sha256:2651dfb006f391bcb240635079a68a261b227a10a08af6349cba834a2141efa1", + "sha256:26811df4dc81271033a7836bc20d12cd30938e6bd2e9437f56fa03da81b0f8fc", + "sha256:26d9c1c4f1748ccac0bae1dbb465fb1a795a75aba8af8ca871503019f4285e2a", + "sha256:28fe80a3eb673b2d5cc3b12eea468a5e5f4603c26aa34d88bf61bba82ceb2f9b", + "sha256:2cd8518eade968bc52262d8c46727cfc0826ff4d552cf0430b8d65aaf50bb91d", + "sha256:2d004db911ed7b6218ec5c5bfe4cf70ae8aa2223dffbb5b3c69e342bb253cb28", + "sha256:3d07c28b85b350564bdff9f51c1c5007dfb2f389385d1bc23288de51134ca303", + "sha256:3e7e6ef1737a819819b1163116ad4b48d06cfdd40352d813bb14436024fcda99", + "sha256:44151d7b81b9391ed759a2f2865bbe623ef00d648fed59363be2bbbd5154656f", + "sha256:44cd313629ded43bb3b98737bba2f3e2c2c8679b55ea29ed73daea6b755fe8e7", + "sha256:4a3dae7492d16e85ea6045fd11cb8e782b63eac8c8d520c3a92c02ac4573b0a6", + "sha256:4b5ea3664eed571779403858d7cd0a9b0ebf50d57d2cdeafc7748e09ef8cd81a", + "sha256:4c3446937be153718250fe421da548f973124189f18fe4575a0510b5c928f0cc", + "sha256:5415b9494ff6240b09af06b91a375731febe0090218e2898d2b85f9b92abcda0", + "sha256:5fd6e94593f6f9714dbad1aaba734b5ec04593374fa6638df61592055868f8b8", + "sha256:619935a44f414274a2c08c9e74611965650b730eb4efe4b2270f91df5e4adf9a", + "sha256:655b21ffd37a96b1e78cc48bf254f5ea4b5b85efaf9e9e2a526b3c9309d660ca", + "sha256:665b21e95bc0fce5cab03b2e1d90ba9c66c510f1bb5fdc864f3a377d0f553f6b", + "sha256:6a4bf607f690f7987ab3291406e012cd8591a4f77aa54f29b890f9c331e84989", + "sha256:6cea1cca3be76c9483282dc7760ea1cc08a6ecec1f0b6ca0a94ea0d17432da19", + "sha256:713d450cf8e61854de9420fb7eea8ad228df4e27e7d4ed465de98c955d2b3fa6", + "sha256:726377bd60081172685c0ff46afbc600d064f01053190e4450857483c4d44484", + "sha256:76b3e3976d2a452cba7aa9e453498ac72240d43030fdc6d538a72b87eaff52fd", + "sha256:76dc19e660baea5c38e949455c1181bc018893f25372d10ffe24b3ed7341fb25", + "sha256:76e5064fd8e94c3f74d9fd69b02d99e3cdb8fc286ed49a1f10b256e59d0d3a0b", + "sha256:7f346d24d74c00b6730440f5eb8ec3fe5774ca8d1c9574e8e57c8671bb51b910", + "sha256:81eeec4403a7d7684b5812a8aaa626fa23b7d0848edb3a28d2eb3220daddcbd0", + "sha256:90b5bbf05fe3d3ef697103850c2ce3374558f6fe40fd57c9fac1bf14903f50a5", + "sha256:9730929375021ec90f6447bff4f7f5508faef1c02f399a1953870cdb78e0c345", + "sha256:9eb4a1d7399b9f3c7ac68ae6baa6be5f9195d1d08c9ddc45ad559aa6b556bce6", + "sha256:a0409bc18a9f85321399c29baf93545152d74a49d92f2f55302f122007cfda00", + "sha256:a22f4e26400f7f48faef2d69c20dc055a1f3043d330923f9abe08ea0aecc44df", + "sha256:a53dfe8f82b715319e9953330fa5c8708b610d48b5c59f1316337302af5c0811", + "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca", + "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8", + "sha256:a8870983af660798dc1b529e1fd6f1cefd94e45135a32e58bd70edd694540f33", + "sha256:ac0adfdb3a21dc2a24ed728b61e72440d297d0fd3a577389df566651fcd08f97", + "sha256:b395121e9bbe8d02a750886f108d540abe66075e61e22f7353d9acb0b81be0f0", + "sha256:b9505a0c8579899057cbefd4ec34d865ab99852baf1ff33a9481eb3924e2da0b", + "sha256:c0a5b1c22c82831f56f2f7ad9bbe4948879762fe0d59833a4a71f16e5fa0f682", + "sha256:c3967dcc1cd2ea61b08b0b276659242cbce5caca39e7cbc02408222fb9e6ff39", + "sha256:c6f4c2027689093775fd58ca2388d58789009116844432d920e9147f91acbe64", + "sha256:c9d86401550b09a55410f32ceb5fe7efcd998bd2dad9e82521713cb148a4a15f", + "sha256:cd468ec62257bb4544989402b19d795d2305eccb06cde5da0eb739b63dc04665", + "sha256:cfcfb73aed40f550a57ea904629bdaf2e562c68fa1164fa4588e752af6efdc3f", + "sha256:d0dd943282231480aad5f50f89bdf26690c995e8ff555f26d8a5b9887b559bcc", + "sha256:d3c59a06c2c28a81a026ff11fbf012081ea34fb9b7052f2ed0366e14896f0a1d", + "sha256:d45b75b0f3fd8d99f62eb7908cfa6d727b7ed190737dec7fe46d993da550b81a", + "sha256:d46d5069e2eeda111d6f71970e341f4bd9aeeee92074e649ae263b834286ecc0", + "sha256:d58ec349e0c2c0bc6669bf2cd4982d2f93bf067860d23a0ea1fe677b0f0b1e09", + "sha256:db1b3ccb93488328c74e97ff888604a8b95ae4f35f4f56677ca57a4fc3a4220b", + "sha256:dd65695a8df1233309b701dec2539cc4b11e97d4fcc0f4185b4a12ce54db0491", + "sha256:f9482c2ed414781c0af0b35d9d575226da6b728bd1a720668fa05837184965b7", + "sha256:f9671e7282d8c6fcabc32c0fb8d7c0ea8894ae85cee89c9aadc2d7129e1a9954", + "sha256:fad7a051e07f64e297e6e8399b4d6a3bdcad3d7297409e9a06ef8cbccff4f501", + "sha256:ffb08f2a1e59d38c7b8b9ac8083c9c8b9875f0955b1e9b9b9a965607a51f8e54" + ], + "markers": "platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", + "version": "==3.1.0" + }, + "identify": { + "hashes": [ + "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf", + "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0" + ], + "markers": "python_version >= '3.8'", + "version": "==2.6.0" + }, + "nodeenv": { + "hashes": [ + "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", + "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==1.9.1" + }, + "numpy": { + "hashes": [ + "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", + "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", + "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", + "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", + "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", + "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", + "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea", + "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c", + "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", + "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", + "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be", + "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", + "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", + "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", + "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", + "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd", + "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c", + "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", + "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0", + "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c", + "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", + "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", + "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", + "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6", + "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", + "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", + "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30", + "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", + "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", + "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", + "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", + "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", + "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764", + "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", + "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", + "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==1.26.4" + }, + "pandas": { + "hashes": [ + "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813", + "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792", + "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406", + "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373", + "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328", + "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996", + "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf", + "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6", + "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7", + "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc", + "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1", + "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23", + "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a", + "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51", + "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572", + "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31", + "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5", + "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a", + "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003", + "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d", + "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354", + "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee", + "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa", + "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0", + "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9", + "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae", + "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.5.3" + }, + "platformdirs": { + "hashes": [ + "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c", + "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.2" + }, + "pre-commit": { + "hashes": [ + "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af", + "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==3.8.0" + }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.9.0.post0" + }, + "pytz": { + "hashes": [ + "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", + "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725" + ], + "version": "==2024.2" + }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, + "ruff": { + "hashes": [ + "sha256:0308610470fcc82969082fc83c76c0d362f562e2f0cdab0586516f03a4e06ec6", + "sha256:0b52387d3289ccd227b62102c24714ed75fbba0b16ecc69a923a37e3b5e0aaaa", + "sha256:0ea086601b22dc5e7693a78f3fcfc460cceabfdf3bdc36dc898792aba48fbad6", + "sha256:34d5efad480193c046c86608dbba2bccdc1c5fd11950fb271f8086e0c763a5d1", + "sha256:50e30b437cebef547bd5c3edf9ce81343e5dd7c737cb36ccb4fe83573f3d392e", + "sha256:549daccee5227282289390b0222d0fbee0275d1db6d514550d65420053021a58", + "sha256:66dbfea86b663baab8fcae56c59f190caba9398df1488164e2df53e216248baa", + "sha256:7862f42fc1a4aca1ea3ffe8a11f67819d183a5693b228f0bb3a531f5e40336fc", + "sha256:803b96dea21795a6c9d5bfa9e96127cc9c31a1987802ca68f35e5c95aed3fc0d", + "sha256:932063a03bac394866683e15710c25b8690ccdca1cf192b9a98260332ca93408", + "sha256:ac3b5bfbee99973f80aa1b7cbd1c9cbce200883bdd067300c22a6cc1c7fba212", + "sha256:ac4b75e898ed189b3708c9ab3fc70b79a433219e1e87193b4f2b77251d058d14", + "sha256:bedff9e4f004dad5f7f76a9d39c4ca98af526c9b1695068198b3bda8c085ef60", + "sha256:c44536df7b93a587de690e124b89bd47306fddd59398a0fb12afd6133c7b3818", + "sha256:c4b153fc152af51855458e79e835fb6b933032921756cec9af7d0ba2aa01a258", + "sha256:d02a4127a86de23002e694d7ff19f905c51e338c72d8e09b56bfb60e1681724f", + "sha256:eebe4ff1967c838a1a9618a5a59a3b0a00406f8d7eefee97c70411fefc353617", + "sha256:f0f8968feea5ce3777c0d8365653d5e91c40c31a81d95824ba61d871a11b8523" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.6.4" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7", + "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8", + "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb", + "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260", + "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0", + "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513", + "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b", + "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2", + "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3", + "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584", + "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255", + "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49", + "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7", + "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9", + "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af", + "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc", + "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e", + "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134", + "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd", + "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf", + "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c", + "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57", + "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa", + "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a", + "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90", + "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e", + "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6", + "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0", + "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb", + "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e", + "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221", + "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13", + "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7", + "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621", + "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a", + "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0", + "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e", + "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5", + "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5", + "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3", + "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797", + "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472", + "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b", + "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953", + "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9", + "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad", + "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46", + "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c", + "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.0.30" + }, + "typing-extensions": { + "hashes": [ + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" + ], + "markers": "python_version >= '3.8'", + "version": "==4.12.2" + }, + "virtualenv": { + "hashes": [ + "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55", + "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c" + ], + "markers": "python_version >= '3.7'", + "version": "==20.26.4" + } + } } diff --git a/README.md b/README.md index d28f5a083..c6f6a127c 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ Le frontend de cette application est écrite en [Elm](https://elm-lang.org/). Vo ### Frontend - $ npm install + $ NODE_ENV=development npm install ### Backend - $ pipenv install + $ pipenv install -d Assurez-vous d'avoir un PostgreSQL >=16 qui tourne localement si vous souhaitez vous rapprocher de l'environnement de production. À défaut, `sqlite` sera utilisé. @@ -49,10 +49,11 @@ Les variables d'environnement suivantes doivent être définies : - `EMAIL_HOST` : le host SMTP pour envoyer les mail liés à l'authentification - `EMAIL_HOST_USER`: l'utilisateur du compte SMTP - `EMAIL_HOST_PASSWORD` : le mot de passe du compte SMTP pour envoyer les mail liés à l'authentification +- `ENABLE_FOOD_SECTION` : affichage ou non de la section expérimentale dédiée à l'alimentaire (valeur `True` ou `False`, par défault `False`) - `MATOMO_HOST`: le domaine de l'instance Matomo permettant le suivi d'audience du produit (typiquement `stats.beta.gouv.fr`). - `MATOMO_SITE_ID`: l'identifiant du site Ecobalyse sur l'instance Matomo permettant le suivi d'audience du produit. - `MATOMO_TOKEN`: le token Matomo permettant le suivi d'audience du produit. -- `NODE_ENV`: l'environnement d'exécution nodejs (par défaut, `production`) +- `NODE_ENV`: l'environnement d'exécution nodejs (par défaut, `development`) - `SCALINGO_POSTGRESQL_URL` : l'uri pour accéder à Postgresl (définie automatiquement par Scalingo). Si non défini sqlite3 est utilisé. - `SENTRY_DSN`: le DSN [Sentry](https://sentry.io) à utiliser pour les rapports d'erreur. diff --git a/bin/build-specific-app-version.sh b/bin/build-specific-app-version.sh index 3b3cd6229..76617e220 100755 --- a/bin/build-specific-app-version.sh +++ b/bin/build-specific-app-version.sh @@ -158,7 +158,7 @@ cd $PUBLIC_GIT_CLONE_DIR # Installing node stuff # We need to specify dev as the env to avoid errors with needed dev packages at build time like # old husky prerequesite -NODE_ENV=dev npm ci +NODE_ENV=development npm ci # We want a production build export NODE_ENV=production diff --git a/buildpack-run.sh b/buildpack-run.sh index af3e5d38f..86ae96d9d 100755 --- a/buildpack-run.sh +++ b/buildpack-run.sh @@ -6,3 +6,7 @@ git clone git@github.com:MTES-MCT/ecobalyse-private.git ./bin/checkout-ecobalyse-private-branch.sh $SOURCE_VERSION ./bin/download_github_releases.py + +# Remove big map files from old versions for a slimer scalingo image +find versions/ -type f -name "*.js.map" -delete +find versions/ -type f -name "*.css.map" -delete diff --git a/check-db.js b/check-db.js new file mode 100644 index 000000000..7250f4e27 --- /dev/null +++ b/check-db.js @@ -0,0 +1,30 @@ +require("dotenv").config(); +const fs = require("fs"); +const { Elm } = require("./check-db-app"); +const lib = require("./lib"); + +const { ECOBALYSE_DATA_DIR } = process.env; + +let dataFiles; +try { + dataFiles = lib.getDataFiles(ECOBALYSE_DATA_DIR); +} catch (err) { + console.error(`🚨 ERROR: ${err.message}`); + process.exit(1); +} + +const elmApp = Elm.CheckDb.init({ + flags: { + textileProcesses: fs.readFileSync(dataFiles.textileDetailed, "utf-8"), + foodProcesses: fs.readFileSync(dataFiles.foodDetailed, "utf-8"), + }, +}); + +elmApp.ports.logAndExit.subscribe(({ message, status }) => { + if (status > 0) { + console.error(`🚨 ERROR: ${message}`); + } else { + console.info(message); + } + process.exit(status); +}); diff --git a/compute-aggregated.js b/compute-aggregated.js index a309ac219..3c487657d 100644 --- a/compute-aggregated.js +++ b/compute-aggregated.js @@ -41,7 +41,7 @@ elmApp.ports.export.subscribe( exportJson(dataFiles.foodNoDetails, foodProcessesOnlyAggregated); console.log(` 4 files exported to: -x" + - ${dataFiles.textileDetailed} - ${dataFiles.foodDetailed} - ${dataFiles.textileNoDetails} diff --git a/data/Makefile b/data/Makefile index b713cb3de..2007dfe98 100644 --- a/data/Makefile +++ b/data/Makefile @@ -12,18 +12,18 @@ env | grep ECOBALYSE_DATA_DIR || exit docker exec -u jovyan -it -e ECOBALYSE_DATA_DIR=/home/jovyan/ecobalyse-private/ -w /home/jovyan/ecobalyse/data $(NAME) $(1);\ else \ echo "(Creating a new container)" &&\ - docker run --rm -it -v $(NAME):/home/jovyan -v $$PWD/../:/home/jovyan/ecobalyse -v $(ECOBALYSE_DATA_DIR):/home/jovyan/ecobalyse-private -e ECOBALYSE_DATA_DIR=/home/jovyan/ecobalyse-private/ -w /home/jovyan/ecobalyse/data $(NAME) $(1); fi + docker run --rm -it -v $(NAME):/home/jovyan -v $$PWD/../:/home/jovyan/ecobalyse -v $$PWD/../../dbfiles/:/home/jovyan/dbfiles -v $(ECOBALYSE_DATA_DIR):/home/jovyan/ecobalyse-private -e ECOBALYSE_DATA_DIR=/home/jovyan/ecobalyse-private/ -w /home/jovyan/ecobalyse/data $(NAME) $(1); fi endef all: import export -import : image import_agribalyse import_ecoinvent import_method sync_datapackages +import : image import_food import_ecoinvent import_method sync_datapackages export: export_food format image: docker build -t $(NAME) docker -import_agribalyse: - @$(call DOCKER,python3 import_agribalyse.py --recreate-activities) +import_food: + @$(call DOCKER,python3 import_food.py --recreate-activities) import_method: @$(call DOCKER,python3 import_method.py) @@ -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/README.md b/data/README.md index da2012c45..403e2c3f4 100644 --- a/data/README.md +++ b/data/README.md @@ -6,8 +6,8 @@ Comment générer les données json utilisées par le frontal elm : - Si vous êtes sur Mac avec architecture ARM, affectez 6Go de RAM à Docker dans Docker Desktop : Settings → Ressources → Advanced → Memory = 6G - Préparez les bases de données à importer, elle ne font pas partie du dépôt : - - Agribalyse : compressé dans un fichier `AGB3.1.1.20230306.CSV.zip` dans ce dossier data/ - - Autres bases alimentaire : consultez les noms de fichier dans `import_agribalyse.py` + - Agribalyse : compressé dans un fichier `AGB3.1.1.20230306.CSV.zip` dans un dossier `dbfiles/` au dessus du dépôt + - Autres bases alimentaire : consultez les noms de fichier dans `import_food.py` - Ecoinvent : décompressé dans un dossier `ECOINVENT3.9.1` dans ce même dossier - Lancez **`make`** ce qui va successivement : - construire l'image docker ; @@ -20,14 +20,14 @@ d'abord un `make clean_data` (qui supprime le volume docker). ## Autres commandes : - `make image` : pour construire l'image docker choisie -- `make import_agribalyse` : pour importer les bases de données alimentaire dans Brightway. - Assurez-vous d'avoir les bon fichiers de données dans `data/` -- `make import_ecoinvent` : pour importer Ecoinvent 3.9.1. dans Brightway. Assurez-vous - d'avoir le bon dossier de données dans `data/` +- `make import_food` : pour importer les bases de données alimentaire dans Brightway. + Assurez-vous d'avoir les bon fichiers de données dans `dbfiles/` au dessus du dépôt +- `make import_ecoinvent` : pour importer Ecoinvent 3.9.1. dans Brightway. + Assurez-vous d'avoir le bon dossier de données dans `dbfiles/` au dessus du dépôt - `make import_method` : pour importer EF 3.1 adapted dans Brightway. - Assurez-vous d'avoir le bon fichier de données dans `data/` + Assurez-vous d'avoir le bon fichier de données dans `dbfiles/` au dessus du dépôt - `make export_food` : pour exporter les json pour le builder alimentaire -- `make delete_database DB=` : pour supprimer une base de données +- `make delete_database DB=` : pour supprimer une base de données (Ex avec espace: make delete_database DB="Ecoinvent\ 3.9.1") - `make delete_method` : pour supprimer la méthode EF3.1 - `make sync_datapackages` : lance un fix parfois nécessaire pour la synchro brightway - `make import` : lance toutes les commandes d'import diff --git a/data/docker/entrypoint.sh b/data/docker/entrypoint.sh index a21d800db..b0b91b5c8 100755 --- a/data/docker/entrypoint.sh +++ b/data/docker/entrypoint.sh @@ -8,6 +8,8 @@ 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 "$@" +exec gosu jovyan "$@" diff --git a/data/import_ecoinvent.py b/data/import_ecoinvent.py index bf554c24a..df70ba327 100755 --- a/data/import_ecoinvent.py +++ b/data/import_ecoinvent.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 +from os.path import join + import bw2data import bw2io from bw2data.project import projects @@ -21,12 +23,12 @@ def main(): add_missing_substances(PROJECT, BIOSPHERE) if (db := "Ecoinvent 3.9.1") not in bw2data.databases: - import_simapro_csv(EI391, db) + import_simapro_csv(join("..", "..", "dbfiles", EI391), db) else: print(f"{db} already imported") if (db := "Ecoinvent 3.10") not in bw2data.databases: - import_simapro_csv(EI310, db) + import_simapro_csv(join("..", "..", "dbfiles", EI310), db) else: print(f"{db} already imported") diff --git a/data/import_agribalyse.py b/data/import_food.py similarity index 94% rename from data/import_agribalyse.py rename to data/import_food.py index 680f0d2fd..55cb32781 100755 --- a/data/import_agribalyse.py +++ b/data/import_food.py @@ -3,6 +3,7 @@ import argparse import copy import functools +from os.path import join import bw2data import bw2io @@ -222,7 +223,7 @@ def remove_some_processes(db): # AGRIBALYSE 3.1.1 if (db := "Agribalyse 3.1.1") not in bw2data.databases: import_simapro_csv( - AGRIBALYSE31, + join("..", "..", "dbfiles", AGRIBALYSE31), db, migrations=AGRIBALYSE_MIGRATIONS, excluded_strategies=EXCLUDED, @@ -234,7 +235,7 @@ def remove_some_processes(db): # AGRIBALYSE 3.2 if (db := "Agribalyse 3.2 beta 08/08/2024") not in bw2data.databases: import_simapro_csv( - AGRIBALYSE32, + join("..", "..", "dbfiles", AGRIBALYSE32), db, migrations=AGRIBALYSE_MIGRATIONS, first_strategies=[remove_some_processes], @@ -247,14 +248,16 @@ def remove_some_processes(db): # PASTO ECO if (db := "PastoEco") not in bw2data.databases: for p in PASTOECO: - import_simapro_csv(p, db, excluded_strategies=EXCLUDED) + import_simapro_csv( + join("..", "..", "dbfiles", p), db, excluded_strategies=EXCLUDED + ) else: print(f"{db} already imported") # GINKO if (db := "Ginko") not in bw2data.databases: import_simapro_csv( - GINKO, + join("..", "..", "dbfiles", GINKO), db, excluded_strategies=EXCLUDED, other_strategies=GINKO_STRATEGIES, @@ -265,13 +268,17 @@ def remove_some_processes(db): # CTCPA if (db := "CTCPA") not in bw2data.databases: - import_simapro_csv(CTCPA, db, excluded_strategies=EXCLUDED) + import_simapro_csv( + join("..", "..", "dbfiles", CTCPA), db, excluded_strategies=EXCLUDED + ) else: print(f"{db} already imported") # WFLDB if (db := "WFLDB") not in bw2data.databases: - import_simapro_csv(WFLDB, db, excluded_strategies=EXCLUDED) + import_simapro_csv( + join("..", "..", "dbfiles", WFLDB), db, excluded_strategies=EXCLUDED + ) else: print(f"{db} already imported") diff --git a/data/import_method.py b/data/import_method.py index 885bf9129..5e0bf2f86 100755 --- a/data/import_method.py +++ b/data/import_method.py @@ -24,7 +24,7 @@ # Agribalyse BIOSPHERE = "biosphere3" METHODNAME = "Environmental Footprint 3.1 (adapted) patch wtu" # defined inside the csv -METHODPATH = METHODNAME + ".CSV.zip" +METHODPATH = os.path.join("..", "..", "dbfiles", METHODNAME + ".CSV.zip") # excluded strategies and migrations EXCLUDED_FOOD = [ diff --git a/data/notebooks/explore.py b/data/notebooks/explore.py index 59ae9d1bd..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) ) @@ -411,7 +413,7 @@ def display_main_data(method, impact_category, activity): name = ", ".join(m[1:]) scores[name] = { "Indicateur": name, - "Amount": lca.score, + "Score": lca.score, "Unité": bw2data.methods[m].get("unit", "(no unit)"), } @@ -428,8 +430,8 @@ def display_main_data(method, impact_category, activity): } scores["Ecotoxicity, freshwater"] = { "Indicateur": "Ecotoxicity, freshwater", - "Amount": scores["Ecotoxicity, freshwater - part 1"]["Amount"] - + scores["Ecotoxicity, freshwater - part 2"]["Amount"], + "Score": scores["Ecotoxicity, freshwater - part 1"]["Score"] + + scores["Ecotoxicity, freshwater - part 2"]["Score"], "Unité": scores["Ecotoxicity, freshwater - part 1"]["Unité"], } for trigram in [ @@ -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/index.js b/index.js index 29d3e7bfa..274c6e86f 100644 --- a/index.js +++ b/index.js @@ -11,11 +11,14 @@ if (process.env.SENTRY_DSN) { allowUrls: [ /^https:\/\/ecobalyse\.beta\.gouv\.fr/, /^https:\/\/staging-ecobalyse\.incubateur\.net/, + // Review apps + /^https:\/\/ecobalyse-pr.*\.osc-fr1\.scalingo\.io/, ], ignoreErrors: [ // Most often due to DOM-aggressive browser extensions /_VirtualDom_applyPatch/, ], + environment: process.env.IS_REVIEW_APP ? "review-app" : process.env.NODE_ENV || "development", }); } diff --git a/lib/instrument.js b/lib/instrument.js index e1bbb5aaf..7dbae099d 100644 --- a/lib/instrument.js +++ b/lib/instrument.js @@ -1,19 +1,23 @@ const Sentry = require("@sentry/node"); const { nodeProfilingIntegration } = require("@sentry/profiling-node"); -const { SENTRY_DSN } = process.env; +const { SENTRY_DSN, IS_REVIEW_APP, NODE_ENV } = process.env; -if (SENTRY_DSN) { +const shouldInitSentry = SENTRY_DSN && NODE_ENV === "production"; + +if (shouldInitSentry) { Sentry.init({ dsn: SENTRY_DSN, integrations: [nodeProfilingIntegration()], tracesSampleRate: 1.0, profilesSampleRate: 1.0, + // IS_REVIEW_APP is set by `scalingo.json` only on review apps + environment: IS_REVIEW_APP ? "review-app" : NODE_ENV, }); } function monitorExpressApp(app) { - if (SENTRY_DSN) { + if (shouldInitSentry) { Sentry.setupExpressErrorHandler(app); } } diff --git a/openapi.yaml b/openapi.yaml index bc67bac13..194b32c4d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -104,6 +104,7 @@ paths: - $ref: "#/components/parameters/priceParam" - $ref: "#/components/parameters/traceabilityParam" - $ref: "#/components/parameters/upcycledParam" + - $ref: "#/components/parameters/physicalDurabilityParam" responses: 200: description: Opération réussie @@ -176,6 +177,7 @@ paths: - $ref: "#/components/parameters/priceParam" - $ref: "#/components/parameters/traceabilityParam" - $ref: "#/components/parameters/upcycledParam" + - $ref: "#/components/parameters/physicalDurabilityParam" responses: 200: description: successful operation @@ -250,6 +252,7 @@ paths: - $ref: "#/components/parameters/priceParam" - $ref: "#/components/parameters/traceabilityParam" - $ref: "#/components/parameters/upcycledParam" + - $ref: "#/components/parameters/physicalDurabilityParam" responses: 200: description: Opération réussie @@ -292,160 +295,160 @@ paths: application/json: schema: $ref: "#/components/schemas/InvalidParametersError" - # /food/countries: - # get: - # tags: - # - Alimentaire - # summary: Liste des pays utilisables pour les simulations alimentairess. - # responses: - # 200: - # description: Opération réussie - # content: - # application/json: - # schema: - # $ref: "#/components/schemas/CountryListResponse" - # /food/ingredients: - # get: - # tags: - # - Alimentaire - # summary: Liste des ingrédients disponibles pour élaborer une recette - # responses: - # 200: - # description: Opération réussie - # content: - # application/json: - # schema: - # $ref: "#/components/schemas/IngredientListResponse" - # /food/transforms: - # get: - # tags: - # - Alimentaire - # summary: Liste des procédés de transformation alimentaire - # responses: - # 200: - # description: Opération réussie - # content: - # application/json: - # schema: - # $ref: "#/components/schemas/TransformListResponse" - # /food/packagings: - # get: - # tags: - # - Alimentaire - # summary: Liste des emballages disponibles pour conditionner une recette - # responses: - # 200: - # description: Opération réussie - # content: - # application/json: - # schema: - # $ref: "#/components/schemas/PackagingListResponse" - # /food: - # get: - # tags: - # - Alimentaire - # summary: Calcul des impacts environnementaux d'une recette alimentaire - # parameters: - # - $ref: "#/components/parameters/ingredientsParam" - # - $ref: "#/components/parameters/transformParam" - # - $ref: "#/components/parameters/packagingParam" - # - $ref: "#/components/parameters/distributionParam" - # - $ref: "#/components/parameters/preparationParam" - # responses: - # 200: - # description: Opération réussie - # content: - # application/json: - # schema: - # $ref: "#/components/schemas/RecipeResultsResponse" - # 400: - # description: Paramètres invalides - # content: - # application/json: - # schema: - # $ref: "#/components/schemas/InvalidParametersError" - # post: - # tags: - # - Alimentaire - # summary: Calcul des impacts environnementaux d'une recette alimentaire - # requestBody: - # description: Requête modélisant les éléments de la recette à évaluer - # required: true - # content: - # application/json: - # schema: - # $ref: "#/components/schemas/FoodQuery" - # examples: - # CarrotCake: - # summary: "Carrot cake conventionnel" - # value: - # ingredients: - # - id: egg - # mass: 120 - # - id: wheat - # mass: 140 - # - id: milk - # mass: 60 - # - id: carrot - # mass: 225 - # transform: - # code: AGRIBALU000000003103966 - # mass: 545 - # packaging: - # - code: AGRIBALU000000003104019 - # mass: 105 - # distribution: ambient - # preparation: - # - refrigeration - # SpanishOrganicCarrotCake: - # summary: "Carrot cake bio, origine Espagne" - # value: - # ingredients: - # - id: egg-organic - # mass: 120 - # country: ES - # - id: wheat-organic - # mass: 140 - # country: ES - # - id: milk-organic - # mass: 60 - # country: ES - # - id: carrot-organic - # mass: 225 - # country: ES - # transform: - # code: AGRIBALU000000003103966 - # mass: 545 - # packaging: - # - code: AGRIBALU000000003104019 - # mass: 105 - # distribution: ambient - # preparation: - # - refrigeration - # BrazilianMango: - # summary: "Mangue du Brésil" - # value: - # ingredients: - # - id: mango - # mass: 500 - # country: BR - # transform: null - # packaging: [] - # distribution: ambient - # preparation: [] - # responses: - # 200: - # description: Opération réussie - # content: - # application/json: - # schema: - # $ref: "#/components/schemas/RecipeResultsResponse" - # 400: - # description: Paramètres invalides - # content: - # application/json: - # schema: - # $ref: "#/components/schemas/InvalidParametersError" + /food/countries: + get: + tags: + - Alimentaire + summary: Liste des pays utilisables pour les simulations alimentairess. + responses: + 200: + description: Opération réussie + content: + application/json: + schema: + $ref: "#/components/schemas/CountryListResponse" + /food/ingredients: + get: + tags: + - Alimentaire + summary: Liste des ingrédients disponibles pour élaborer une recette + responses: + 200: + description: Opération réussie + content: + application/json: + schema: + $ref: "#/components/schemas/IngredientListResponse" + /food/transforms: + get: + tags: + - Alimentaire + summary: Liste des procédés de transformation alimentaire + responses: + 200: + description: Opération réussie + content: + application/json: + schema: + $ref: "#/components/schemas/TransformListResponse" + /food/packagings: + get: + tags: + - Alimentaire + summary: Liste des emballages disponibles pour conditionner une recette + responses: + 200: + description: Opération réussie + content: + application/json: + schema: + $ref: "#/components/schemas/PackagingListResponse" + /food: + get: + tags: + - Alimentaire + summary: Calcul des impacts environnementaux d'une recette alimentaire + parameters: + - $ref: "#/components/parameters/ingredientsParam" + - $ref: "#/components/parameters/transformParam" + - $ref: "#/components/parameters/packagingParam" + - $ref: "#/components/parameters/distributionParam" + - $ref: "#/components/parameters/preparationParam" + responses: + 200: + description: Opération réussie + content: + application/json: + schema: + $ref: "#/components/schemas/RecipeResultsResponse" + 400: + description: Paramètres invalides + content: + application/json: + schema: + $ref: "#/components/schemas/InvalidParametersError" + post: + tags: + - Alimentaire + summary: Calcul des impacts environnementaux d'une recette alimentaire + requestBody: + description: Requête modélisant les éléments de la recette à évaluer + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/FoodQuery" + examples: + CarrotCake: + summary: "Carrot cake conventionnel" + value: + ingredients: + - id: egg + mass: 120 + - id: wheat + mass: 140 + - id: milk + mass: 60 + - id: carrot + mass: 225 + transform: + code: AGRIBALU000000003103966 + mass: 545 + packaging: + - code: AGRIBALU000000003104019 + mass: 105 + distribution: ambient + preparation: + - refrigeration + SpanishOrganicCarrotCake: + summary: "Carrot cake bio, origine Espagne" + value: + ingredients: + - id: egg-organic + mass: 120 + country: ES + - id: wheat-organic + mass: 140 + country: ES + - id: milk-organic + mass: 60 + country: ES + - id: carrot-organic + mass: 225 + country: ES + transform: + code: AGRIBALU000000003103966 + mass: 545 + packaging: + - code: AGRIBALU000000003104019 + mass: 105 + distribution: ambient + preparation: + - refrigeration + BrazilianMango: + summary: "Mangue du Brésil" + value: + ingredients: + - id: mango + mass: 500 + country: BR + transform: null + packaging: [] + distribution: ambient + preparation: [] + responses: + 200: + description: Opération réussie + content: + application/json: + schema: + $ref: "#/components/schemas/RecipeResultsResponse" + 400: + description: Paramètres invalides + content: + application/json: + schema: + $ref: "#/components/schemas/InvalidParametersError" components: securitySchemes: token: @@ -1099,6 +1102,16 @@ components: freezingAndOven: summary: Congélation et cuisson au four value: ["freezing", "oven"] + physicalDurabilityParam: + name: physicalDurability + in: query + description: La durabilité physique du produit. + required: false + style: form + schema: + type: number + minimum: 0.67 + maximum: 1.45 schemas: Impacts: type: object @@ -1283,6 +1296,11 @@ components: description: | Produit remanufacturé type: boolean + physicalDurability: + description: La durabilité physique du produit. + type: number + minimum: 0.67 + maximum: 1.45 TextileQueryMaterial: type: object additionalProperties: false diff --git a/package-lock.json b/package-lock.json index d7e27062d..bde2c5768 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,50 +1,50 @@ { "name": "ecobalyse", - "version": "2.1.1", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ecobalyse", - "version": "2.1.1", + "version": "2.2.0", "license": "MIT", "dependencies": { - "@parcel/transformer-elm": "^2.12.0", - "@parcel/transformer-image": "^2.12.0", - "@parcel/transformer-sass": "^2.12.0", "@sentry/browser": "^8.27.0", "@sentry/node": "^8.27.0", "@sentry/profiling-node": "^8.28.0", "@sentry/tracing": "^7.114.0", - "bootstrap": "^5.3.3", "cors": "^2.8.5", "dotenv": "^16.4.5", - "elm": "^0.19.1-6", - "express": "^4.19.2", + "express": "^4.21.0", "express-rate-limit": "^7.4.0", "helmet": "^7.1.0", - "highcharts": "^11.4.8", "js-yaml": "^4.1.0", - "parcel": "^2.12.0", "piwik": "^1.0.9" }, "devDependencies": { "@apidevtools/swagger-cli": "^4.0.4", + "@parcel/transformer-elm": "^2.12.0", + "@parcel/transformer-image": "^2.12.0", + "@parcel/transformer-sass": "^2.12.0", + "bootstrap": "^5.3.3", "concurrently": "^8.2.2", + "elm": "^0.19.1-6", "elm-format": "^0.8.7", "elm-json": "^0.2.13", "elm-review": "^2.12.0", "elm-test": "0.19.1-revision12", + "highcharts": "^11.4.8", "jest": "^29.7.0", "nodemon": "^3.1.4", "npm-check-updates": "^17.1.0", + "parcel": "^2.12.0", "prettier": "^3.3.3", "process": "^0.11.10", "rimraf": "^6.0.1", "supertest": "^7.0.0" }, "engines": { - "node": ">=20.0.0" + "node": "20.17" } }, "node_modules/@ampproject/remapping": { @@ -354,6 +354,7 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -366,6 +367,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -377,6 +379,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -390,6 +393,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -397,12 +401,14 @@ "node_modules/@babel/code-frame/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -411,6 +417,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -419,6 +426,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -648,6 +656,7 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -679,6 +688,7 @@ "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -692,6 +702,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -703,6 +714,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -716,6 +728,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -723,12 +736,14 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -737,6 +752,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -745,6 +761,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -1038,6 +1055,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1050,6 +1068,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1062,6 +1081,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1074,6 +1094,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1517,6 +1538,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -1530,6 +1552,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "engines": { "node": ">=6.0.0" } @@ -1538,6 +1561,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, "engines": { "node": ">=6.0.0" } @@ -1546,6 +1570,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -1554,12 +1579,14 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1575,12 +1602,14 @@ "node_modules/@lezer/common": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz", - "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==" + "integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==", + "dev": true }, "node_modules/@lezer/lr": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz", "integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==", + "dev": true, "dependencies": { "@lezer/common": "^1.0.0" } @@ -1592,6 +1621,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1604,6 +1634,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1616,6 +1647,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1628,6 +1660,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1640,6 +1673,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1652,6 +1686,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1661,6 +1696,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.1.tgz", "integrity": "sha512-iA7+tyVqfrATAIsIRWQG+a7ZLLD0VaOCKV2Wd/v4mqIU3J9c4jx9p7S0nw1XH3gJCKNBOOwACOPYYSUu9pgT+w==", + "dev": true, "dependencies": { "@lezer/common": "^1.0.0", "@lezer/lr": "^1.0.0", @@ -1677,6 +1713,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1689,6 +1726,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1701,6 +1739,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1713,6 +1752,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1725,6 +1765,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1737,6 +1778,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -2230,6 +2272,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/bundler-default/-/bundler-default-2.12.0.tgz", "integrity": "sha512-3ybN74oYNMKyjD6V20c9Gerdbh7teeNvVMwIoHIQMzuIFT6IGX53PyOLlOKRLbjxMc0TMimQQxIt2eQqxR5LsA==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/graph": "3.2.0", @@ -2251,6 +2294,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.12.0.tgz", "integrity": "sha512-FX5ZpTEkxvq/yvWklRHDESVRz+c7sLTXgFuzz6uEnBcXV38j6dMSikflNpHA6q/L4GKkCqRywm9R6XQwhwIMyw==", + "dev": true, "dependencies": { "@parcel/fs": "2.12.0", "@parcel/logger": "2.12.0", @@ -2272,6 +2316,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.12.0.tgz", "integrity": "sha512-v2VmneILFiHZJTxPiR7GEF1wey1/IXPdZMcUlNXBiPZyWDfcuNgGGVQkx/xW561rULLIvDPharOMdxz5oHOKQg==", + "dev": true, "dependencies": { "chalk": "^4.1.0" }, @@ -2287,6 +2332,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/compressor-raw/-/compressor-raw-2.12.0.tgz", "integrity": "sha512-h41Q3X7ZAQ9wbQ2csP8QGrwepasLZdXiuEdpUryDce6rF9ZiHoJ97MRpdLxOhOPyASTw/xDgE1xyaPQr0Q3f5A==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0" }, @@ -2303,6 +2349,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/config-default/-/config-default-2.12.0.tgz", "integrity": "sha512-dPNe2n9eEsKRc1soWIY0yToMUPirPIa2QhxcCB3Z5RjpDGIXm0pds+BaiqY6uGLEEzsjhRO0ujd4v2Rmm0vuFg==", + "dev": true, "dependencies": { "@parcel/bundler-default": "2.12.0", "@parcel/compressor-raw": "2.12.0", @@ -2348,6 +2395,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.12.0.tgz", "integrity": "sha512-s+6pwEj+GfKf7vqGUzN9iSEPueUssCCQrCBUlcAfKrJe0a22hTUCjewpB0I7lNrCIULt8dkndD+sMdOrXsRl6Q==", + "dev": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", "@parcel/cache": "2.12.0", @@ -2387,6 +2435,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", + "dev": true, "engines": { "node": ">=6" } @@ -2395,6 +2444,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.12.0.tgz", "integrity": "sha512-8f1NOsSFK+F4AwFCKynyIu9Kr/uWHC+SywAv4oS6Bv3Acig0gtwUjugk0C9UaB8ztBZiW5TQZhw+uPZn9T/lJA==", + "dev": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", "nullthrows": "^1.1.1" @@ -2411,6 +2461,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.12.0.tgz", "integrity": "sha512-nmAAEIKLjW1kB2cUbCYSmZOGbnGj8wCzhqnK727zCCWaA25ogzAtt657GPOeFyqW77KyosU728Tl63Fc8hphIA==", + "dev": true, "engines": { "node": ">= 12.0.0" }, @@ -2423,6 +2474,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.12.0.tgz", "integrity": "sha512-NnFkuvou1YBtPOhTdZr44WN7I60cGyly2wpHzqRl62yhObyi1KvW0SjwOMa0QGNcBOIzp4G0CapoZ93hD0RG5Q==", + "dev": true, "dependencies": { "@parcel/rust": "2.12.0", "@parcel/types": "2.12.0", @@ -2445,6 +2497,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.2.0.tgz", "integrity": "sha512-xlrmCPqy58D4Fg5umV7bpwDx5Vyt7MlnQPxW68vae5+BA4GSWetfZt+Cs5dtotMG2oCHzZxhIPt7YZ7NRyQzLA==", + "dev": true, "dependencies": { "nullthrows": "^1.1.1" }, @@ -2460,6 +2513,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.12.0.tgz", "integrity": "sha512-cJ7Paqa7/9VJ7C+KwgJlwMqTQBOjjn71FbKk0G07hydUEBISU2aDfmc/52o60ErL9l+vXB26zTrIBanbxS8rVg==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/events": "2.12.0" @@ -2476,6 +2530,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.12.0.tgz", "integrity": "sha512-WZz3rzL8k0H3WR4qTHX6Ic8DlEs17keO9gtD4MNGyMNQbqQEvQ61lWJaIH0nAtgEetu0SOITiVqdZrb8zx/M7w==", + "dev": true, "dependencies": { "chalk": "^4.1.0" }, @@ -2491,6 +2546,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/namer-default/-/namer-default-2.12.0.tgz", "integrity": "sha512-9DNKPDHWgMnMtqqZIMiEj/R9PNWW16lpnlHjwK3ciRlMPgjPJ8+UNc255teZODhX0T17GOzPdGbU/O/xbxVPzA==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -2509,6 +2565,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.3.0.tgz", "integrity": "sha512-rhPW9DYPEIqQBSlYzz3S0AjXxjN6Ub2yS6tzzsW/4S3Gpsgk/uEq4ZfxPvoPf/6TgZndVxmKwpmxaKtGMmf3cA==", + "dev": true, "dependencies": { "@mischnic/json-sourcemap": "^0.1.0", "@parcel/diagnostic": "2.12.0", @@ -2530,6 +2587,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/optimizer-css/-/optimizer-css-2.12.0.tgz", "integrity": "sha512-ifbcC97fRzpruTjaa8axIFeX4MjjSIlQfem3EJug3L2AVqQUXnM1XO8L0NaXGNLTW2qnh1ZjIJ7vXT/QhsphsA==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -2552,6 +2610,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.12.0.tgz", "integrity": "sha512-MfPMeCrT8FYiOrpFHVR+NcZQlXAptK2r4nGJjfT+ndPBhEEZp4yyL7n1y7HfX9geg5altc4WTb4Gug7rCoW8VQ==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "htmlnano": "^2.0.0", @@ -2572,6 +2631,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, "engines": { "node": ">= 10" } @@ -2580,6 +2640,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", @@ -2595,6 +2656,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" @@ -2607,6 +2669,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, "dependencies": { "css-tree": "^1.1.2" }, @@ -2617,12 +2680,14 @@ "node_modules/@parcel/optimizer-htmlnano/node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true }, "node_modules/@parcel/optimizer-htmlnano/node_modules/svgo": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", @@ -2643,6 +2708,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/optimizer-image/-/optimizer-image-2.12.0.tgz", "integrity": "sha512-bo1O7raeAIbRU5nmNVtx8divLW9Xqn0c57GVNGeAK4mygnQoqHqRZ0mR9uboh64pxv6ijXZHPhKvU9HEpjPjBQ==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -2666,6 +2732,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/optimizer-svgo/-/optimizer-svgo-2.12.0.tgz", "integrity": "sha512-Kyli+ZZXnoonnbeRQdoWwee9Bk2jm/49xvnfb+2OO8NN0d41lblBoRhOyFiScRnJrw7eVl1Xrz7NTkXCIO7XFQ==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -2685,6 +2752,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, "engines": { "node": ">= 10" } @@ -2693,6 +2761,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", @@ -2708,6 +2777,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" @@ -2720,6 +2790,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, "dependencies": { "css-tree": "^1.1.2" }, @@ -2730,12 +2801,14 @@ "node_modules/@parcel/optimizer-svgo/node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true }, "node_modules/@parcel/optimizer-svgo/node_modules/svgo": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", @@ -2756,6 +2829,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/optimizer-swc/-/optimizer-swc-2.12.0.tgz", "integrity": "sha512-iBi6LZB3lm6WmbXfzi8J3DCVPmn4FN2lw7DGXxUXu7MouDPVWfTsM6U/5TkSHJRNRogZ2gqy5q9g34NPxHbJcw==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -2777,6 +2851,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.12.0.tgz", "integrity": "sha512-0nvAezcjPx9FT+hIL+LS1jb0aohwLZXct7jAh7i0MLMtehOi0z1Sau+QpgMlA9rfEZZ1LIeFdnZZwqSy7Ccspw==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/fs": "2.12.0", @@ -2803,6 +2878,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/packager-css/-/packager-css-2.12.0.tgz", "integrity": "sha512-j3a/ODciaNKD19IYdWJT+TP+tnhhn5koBGBWWtrKSu0UxWpnezIGZetit3eE+Y9+NTePalMkvpIlit2eDhvfJA==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -2824,6 +2900,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/packager-html/-/packager-html-2.12.0.tgz", "integrity": "sha512-PpvGB9hFFe+19NXGz2ApvPrkA9GwEqaDAninT+3pJD57OVBaxB8U+HN4a5LICKxjUppPPqmrLb6YPbD65IX4RA==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/types": "2.12.0", @@ -2844,6 +2921,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/packager-js/-/packager-js-2.12.0.tgz", "integrity": "sha512-viMF+FszITRRr8+2iJyk+4ruGiL27Y6AF7hQ3xbJfzqnmbOhGFtLTQwuwhOLqN/mWR2VKdgbLpZSarWaO3yAMg==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -2867,6 +2945,7 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -2881,6 +2960,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, "engines": { "node": ">=10" }, @@ -2892,6 +2972,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/packager-raw/-/packager-raw-2.12.0.tgz", "integrity": "sha512-tJZqFbHqP24aq1F+OojFbQIc09P/u8HAW5xfndCrFnXpW4wTgM3p03P0xfw3gnNq+TtxHJ8c3UFE5LnXNNKhYA==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0" }, @@ -2908,6 +2989,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/packager-svg/-/packager-svg-2.12.0.tgz", "integrity": "sha512-ldaGiacGb2lLqcXas97k8JiZRbAnNREmcvoY2W2dvW4loVuDT9B9fU777mbV6zODpcgcHWsLL3lYbJ5Lt3y9cg==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/types": "2.12.0", @@ -2927,6 +3009,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/packager-wasm/-/packager-wasm-2.12.0.tgz", "integrity": "sha512-fYqZzIqO9fGYveeImzF8ll6KRo2LrOXfD+2Y5U3BiX/wp9wv17dz50QLDQm9hmTcKGWxK4yWqKQh+Evp/fae7A==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0" }, @@ -2943,6 +3026,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.12.0.tgz", "integrity": "sha512-nc/uRA8DiMoe4neBbzV6kDndh/58a4wQuGKw5oEoIwBCHUvE2W8ZFSu7ollSXUGRzfacTt4NdY8TwS73ScWZ+g==", + "dev": true, "dependencies": { "@parcel/types": "2.12.0" }, @@ -2958,6 +3042,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.12.0.tgz", "integrity": "sha512-q53fvl5LDcFYzMUtSusUBZSjQrKjMlLEBgKeQHFwkimwR1mgoseaDBDuNz0XvmzDzF1UelJ02TUKCGacU8W2qA==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/events": "2.12.0", @@ -2975,6 +3060,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.12.0.tgz", "integrity": "sha512-TqKsH4GVOLPSCanZ6tcTPj+rdVHERnt5y4bwTM82cajM21bCX1Ruwp8xOKU+03091oV2pv5ieB18pJyRF7IpIw==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/types": "2.12.0", @@ -2995,6 +3081,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/reporter-dev-server/-/reporter-dev-server-2.12.0.tgz", "integrity": "sha512-tIcDqRvAPAttRlTV28dHcbWT5K2r/MBFks7nM4nrEDHWtnrCwimkDmZTc1kD8QOCCjGVwRHcQybpHvxfwol6GA==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/utils": "2.12.0" @@ -3012,6 +3099,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/reporter-tracer/-/reporter-tracer-2.12.0.tgz", "integrity": "sha512-g8rlu9GxB8Ut/F8WGx4zidIPQ4pcYFjU9bZO+fyRIPrSUFH2bKijCnbZcr4ntqzDGx74hwD6cCG4DBoleq2UlQ==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/utils": "2.12.0", @@ -3031,6 +3119,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/resolver-default/-/resolver-default-2.12.0.tgz", "integrity": "sha512-uuhbajTax37TwCxu7V98JtRLiT6hzE4VYSu5B7Qkauy14/WFt2dz6GOUXPgVsED569/hkxebPx3KCMtZW6cHHA==", + "dev": true, "dependencies": { "@parcel/node-resolver-core": "3.3.0", "@parcel/plugin": "2.12.0" @@ -3048,6 +3137,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.12.0.tgz", "integrity": "sha512-4ZLp2FWyD32r0GlTulO3+jxgsA3oO1P1b5oO2IWuWilfhcJH5LTiazpL5YdusUjtNn9PGN6QLAWfxmzRIfM+Ow==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/utils": "2.12.0" @@ -3065,6 +3155,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/runtime-js/-/runtime-js-2.12.0.tgz", "integrity": "sha512-sBerP32Z1crX5PfLNGDSXSdqzlllM++GVnVQVeM7DgMKS8JIFG3VLi28YkX+dYYGtPypm01JoIHCkvwiZEcQJg==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -3084,6 +3175,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.12.0.tgz", "integrity": "sha512-SCHkcczJIDFTFdLTzrHTkQ0aTrX3xH6jrA4UsCBL6ji61+w+ohy4jEEe9qCgJVXhnJfGLE43HNXek+0MStX+Mw==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/utils": "2.12.0", @@ -3103,6 +3195,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/runtime-service-worker/-/runtime-service-worker-2.12.0.tgz", "integrity": "sha512-BXuMBsfiwpIEnssn+jqfC3jkgbS8oxeo3C7xhSQsuSv+AF2FwY3O3AO1c1RBskEW3XrBLNINOJujroNw80VTKA==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/utils": "2.12.0", @@ -3121,6 +3214,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/rust/-/rust-2.12.0.tgz", "integrity": "sha512-005cldMdFZFDPOjbDVEXcINQ3wT4vrxvSavRWI3Az0e3E18exO/x/mW9f648KtXugOXMAqCEqhFHcXECL9nmMw==", + "dev": true, "engines": { "node": ">= 12.0.0" }, @@ -3133,6 +3227,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", + "dev": true, "dependencies": { "detect-libc": "^1.0.3" }, @@ -3144,6 +3239,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-babel/-/transformer-babel-2.12.0.tgz", "integrity": "sha512-zQaBfOnf/l8rPxYGnsk/ufh/0EuqvmnxafjBIpKZ//j6rGylw5JCqXSb1QvvAqRYruKeccxGv7+HrxpqKU6V4A==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -3167,6 +3263,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-css/-/transformer-css-2.12.0.tgz", "integrity": "sha512-vXhOqoAlQGATYyQ433Z1DXKmiKmzOAUmKysbYH3FD+LKEKLMEl/pA14goqp00TW+A/EjtSKKyeMyHlMIIUqj4Q==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -3189,6 +3286,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-elm/-/transformer-elm-2.12.0.tgz", "integrity": "sha512-qqVTsP860FghYTLBrJkrFKD7qbNUq/EqUCofFKLWgWSrMXfzTmsNwMCOStTF4NyclLWcC9KBVjNU+fGS5I519Q==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -3215,6 +3313,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-html/-/transformer-html-2.12.0.tgz", "integrity": "sha512-5jW4dFFBlYBvIQk4nrH62rfA/G/KzVzEDa6S+Nne0xXhglLjkm64Ci9b/d4tKZfuGWUbpm2ASAq8skti/nfpXw==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -3239,6 +3338,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.12.0.tgz", "integrity": "sha512-8hXrGm2IRII49R7lZ0RpmNk27EhcsH+uNKsvxuMpXPuEnWgC/ha/IrjaI29xCng1uGur74bJF43NUSQhR4aTdw==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/utils": "2.12.0", @@ -3257,6 +3357,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-js/-/transformer-js-2.12.0.tgz", "integrity": "sha512-OSZpOu+FGDbC/xivu24v092D9w6EGytB3vidwbdiJ2FaPgfV7rxS0WIUjH4I0OcvHAcitArRXL0a3+HrNTdQQw==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -3285,12 +3386,14 @@ "node_modules/@parcel/transformer-js/node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "node_modules/@parcel/transformer-json": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-json/-/transformer-json-2.12.0.tgz", "integrity": "sha512-Utv64GLRCQILK5r0KFs4o7I41ixMPllwOLOhkdjJKvf1hZmN6WqfOmB1YLbWS/y5Zb/iB52DU2pWZm96vLFQZQ==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "json5": "^2.2.0" @@ -3308,6 +3411,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-postcss/-/transformer-postcss-2.12.0.tgz", "integrity": "sha512-FZqn+oUtiLfPOn67EZxPpBkfdFiTnF4iwiXPqvst3XI8H+iC+yNgzmtJkunOOuylpYY6NOU5jT8d7saqWSDv2Q==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -3331,6 +3435,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-posthtml/-/transformer-posthtml-2.12.0.tgz", "integrity": "sha512-z6Z7rav/pcaWdeD+2sDUcd0mmNZRUvtHaUGa50Y2mr+poxrKilpsnFMSiWBT+oOqPt7j71jzDvrdnAF4XkCljg==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/utils": "2.12.0", @@ -3353,6 +3458,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-raw/-/transformer-raw-2.12.0.tgz", "integrity": "sha512-Ht1fQvXxix0NncdnmnXZsa6hra20RXYh1VqhBYZLsDfkvGGFnXIgO03Jqn4Z8MkKoa0tiNbDhpKIeTjyclbBxQ==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0" }, @@ -3369,6 +3475,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.12.0.tgz", "integrity": "sha512-GE8gmP2AZtkpBIV5vSCVhewgOFRhqwdM5Q9jNPOY5PKcM3/Ff0qCqDiTzzGLhk0/VMBrdjssrfZkVx6S/lHdJw==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/utils": "2.12.0", @@ -3387,6 +3494,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-sass/-/transformer-sass-2.12.0.tgz", "integrity": "sha512-xLLoSLPST+2AHJwFRLl4foArDjjy6P1RChP3TxMU2MVS1sbKGJnfFhFpHAacH8ASjuGtu5rbpfpHRZePlvoZxw==", + "dev": true, "dependencies": { "@parcel/plugin": "2.12.0", "@parcel/source-map": "^2.1.1", @@ -3405,6 +3513,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/transformer-svg/-/transformer-svg-2.12.0.tgz", "integrity": "sha512-cZJqGRJ4JNdYcb+vj94J7PdOuTnwyy45dM9xqbIMH+HSiiIkfrMsdEwYft0GTyFTdsnf+hdHn3tau7Qa5hhX+A==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/plugin": "2.12.0", @@ -3428,6 +3537,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.12.0.tgz", "integrity": "sha512-8zAFiYNCwNTQcglIObyNwKfRYQK5ELlL13GuBOrSMxueUiI5ylgsGbTS1N7J3dAGZixHO8KhHGv5a71FILn9rQ==", + "dev": true, "dependencies": { "@parcel/cache": "2.12.0", "@parcel/diagnostic": "2.12.0", @@ -3442,6 +3552,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.12.0.tgz", "integrity": "sha512-z1JhLuZ8QmDaYoEIuUCVZlhcFrS7LMfHrb2OCRui5SQFntRWBH2fNM6H/fXXUkT9SkxcuFP2DUA6/m4+Gkz72g==", + "dev": true, "dependencies": { "@parcel/codeframe": "2.12.0", "@parcel/diagnostic": "2.12.0", @@ -3464,6 +3575,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", + "dev": true, "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", @@ -3499,6 +3611,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -3518,6 +3631,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -3537,6 +3651,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -3556,6 +3671,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -3575,6 +3691,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3594,6 +3711,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3613,6 +3731,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3632,6 +3751,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3651,6 +3771,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -3670,6 +3791,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -3689,6 +3811,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -3708,6 +3831,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -3724,6 +3848,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.12.0.tgz", "integrity": "sha512-zv5We5Jmb+ZWXlU6A+AufyjY4oZckkxsZ8J4dvyWL0W8IQvGO1JB4FGeryyttzQv3RM3OxcN/BpTGPiDG6keBw==", + "dev": true, "dependencies": { "@parcel/diagnostic": "2.12.0", "@parcel/logger": "2.12.0", @@ -3758,6 +3883,7 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, "peer": true, "funding": { "type": "opencollective", @@ -4181,6 +4307,7 @@ "version": "1.4.6", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.6.tgz", "integrity": "sha512-A7iK9+1qzTCIuc3IYcS8gPHCm9bZVKUJrfNnwveZYyo6OFp3jLno4WOM2yBy5uqedgYATEiWgBYHKq37KrU6IA==", + "dev": true, "hasInstallScript": true, "dependencies": { "@swc/counter": "^0.1.2", @@ -4221,6 +4348,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -4236,6 +4364,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -4251,6 +4380,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4266,6 +4396,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4281,6 +4412,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4296,6 +4428,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4311,6 +4444,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -4326,6 +4460,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -4341,6 +4476,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -4356,6 +4492,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -4367,12 +4504,14 @@ "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true }, "node_modules/@swc/helpers": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.6.tgz", "integrity": "sha512-aYX01Ke9hunpoCexYAgQucEpARGQ5w/cqHFrIR+e9gdKb1QWTsVJuTJ2ozQzIAxLyRQe/m+2RqzkyOOGiMKRQA==", + "dev": true, "dependencies": { "tslib": "^2.4.0" } @@ -4380,7 +4519,8 @@ "node_modules/@swc/types": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", - "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==" + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", @@ -4398,6 +4538,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, "engines": { "node": ">=10.13.0" } @@ -4594,7 +4735,8 @@ "node_modules/abortcontroller-polyfill": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" + "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==", + "dev": true }, "node_modules/accepts": { "version": "1.3.8", @@ -4682,6 +4824,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4696,6 +4839,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -4888,12 +5032,14 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/base-x": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dev": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -4944,6 +5090,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, "engines": { "node": ">=8" } @@ -4976,9 +5123,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4988,7 +5135,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -5001,12 +5148,14 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true }, "node_modules/bootstrap": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "dev": true, "funding": [ { "type": "github", @@ -5025,6 +5174,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5034,6 +5184,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -5045,6 +5196,7 @@ "version": "4.23.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5108,7 +5260,8 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true }, "node_modules/buffers": { "version": "0.1.1", @@ -5183,6 +5336,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { "node": ">=6" } @@ -5200,6 +5354,7 @@ "version": "1.0.30001657", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001657.tgz", "integrity": "sha512-DPbJAlP8/BAXy3IgiWmZKItubb3TYGP0WscQQlVGIfT4s/YlFYVuJgyOsQNP7rJRChx/qdMeLJQJP0Sgg2yjNA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5238,6 +5393,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -5262,6 +5418,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -5294,6 +5451,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, "engines": { "node": ">=6.0" } @@ -5377,6 +5535,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, "engines": { "node": ">=0.8" } @@ -5413,6 +5572,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -5423,7 +5583,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -5440,7 +5601,8 @@ "node_modules/command-exists": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true }, "node_modules/commander": { "version": "9.5.0", @@ -5464,7 +5626,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/concurrently": { "version": "8.2.2", @@ -5575,6 +5738,7 @@ "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", @@ -5621,6 +5785,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5634,6 +5799,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -5651,6 +5817,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -5666,6 +5833,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -5682,6 +5850,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -5697,6 +5866,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "optional": true, "peer": true, "engines": { @@ -5710,6 +5880,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -5724,6 +5895,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, "engines": { "node": ">= 6" }, @@ -5735,6 +5907,7 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -5749,6 +5922,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -5764,6 +5938,7 @@ "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, "optional": true, "peer": true }, @@ -5939,6 +6114,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, "bin": { "detect-libc": "bin/detect-libc.js" }, @@ -5992,6 +6168,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", @@ -6005,6 +6182,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -6013,6 +6191,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, "funding": [ { "type": "github", @@ -6024,6 +6203,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, "dependencies": { "domelementtype": "^2.2.0" }, @@ -6038,6 +6218,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", @@ -6061,7 +6242,8 @@ "node_modules/dotenv-expand": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true }, "node_modules/eastasianwidth": { "version": "0.2.0", @@ -6094,12 +6276,14 @@ "node_modules/electron-to-chromium": { "version": "1.4.698", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.698.tgz", - "integrity": "sha512-f9iZD1t3CLy1AS6vzM5EKGa6p9pRcOeEFXRFbaG2Ta+Oe7MkfRQ3fsvPYidzHe1h4i0JvIvpcY55C+B6BZNGtQ==" + "integrity": "sha512-f9iZD1t3CLy1AS6vzM5EKGa6p9pRcOeEFXRFbaG2Ta+Oe7MkfRQ3fsvPYidzHe1h4i0JvIvpcY55C+B6BZNGtQ==", + "dev": true }, "node_modules/elm": { "version": "0.19.1-6", "resolved": "https://registry.npmjs.org/elm/-/elm-0.19.1-6.tgz", "integrity": "sha512-mKYyierHICPdMx/vhiIacdPmTPnh889gjHOZ75ZAoCxo3lZmSWbGP8HMw78wyctJH0HwvTmeKhlYSWboQNYPeQ==", + "dev": true, "hasInstallScript": true, "bin": { "elm": "bin/elm" @@ -6134,7 +6318,8 @@ "node_modules/elm-hot": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/elm-hot/-/elm-hot-1.1.6.tgz", - "integrity": "sha512-zYZJlfs7Gt4BdjA+D+857K+XAWzwwySJmXCgFpHW1dIEfaHSZCIPYPf7/jinZBLfKRkOAlKzI32AA84DY50g7Q==" + "integrity": "sha512-zYZJlfs7Gt4BdjA+D+857K+XAWzwwySJmXCgFpHW1dIEfaHSZCIPYPf7/jinZBLfKRkOAlKzI32AA84DY50g7Q==", + "dev": true }, "node_modules/elm-json": { "version": "0.2.13", @@ -6368,9 +6553,9 @@ "dev": true }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -6388,6 +6573,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, "engines": { "node": ">=0.12" }, @@ -6399,6 +6585,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -6426,6 +6613,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, "engines": { "node": ">=6" } @@ -6526,36 +6714,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6671,6 +6859,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -6679,12 +6868,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -6699,6 +6888,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/find-elm-dependencies/-/find-elm-dependencies-2.0.4.tgz", "integrity": "sha512-x/4w4fVmlD2X4PD9oQ+yh9EyaQef6OtEULdMGBTuWx0Nkppvo2Z/bAiQioW2n+GdRYKypME2b9OmYTw5tw5qDg==", + "dev": true, "dependencies": { "firstline": "^1.2.0", "lodash": "^4.17.19" @@ -6727,6 +6917,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/firstline/-/firstline-1.3.1.tgz", "integrity": "sha512-ycwgqtoxujz1dm0kjkBFOPQMESxB9uKc/PlD951dQDIG+tBXRpYZC2UmJb0gDxopQ1ZX6oyRQN3goRczYu7Deg==", + "dev": true, "engines": { "node": ">=6.4.0" } @@ -6873,12 +7064,14 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -6945,6 +7138,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", + "dev": true, "engines": { "node": ">=6" } @@ -6977,6 +7171,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6996,6 +7191,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -7007,6 +7203,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7125,6 +7322,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -7195,6 +7393,7 @@ "version": "11.4.8", "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-11.4.8.tgz", "integrity": "sha512-5Tke9LuzZszC4osaFisxLIcw7xgNGz4Sy3Jc9pRMV+ydm6sYqsPYdU8ELOgpzGNrbrRNDRBtveoR5xS3SzneEA==", + "dev": true, "license": "https://www.highcharts.com/license" }, "node_modules/html-escaper": { @@ -7207,6 +7406,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.0.tgz", "integrity": "sha512-jVGRE0Ep9byMBKEu0Vxgl8dhXYOUk0iNQ2pjsG+BcRB0u0oDF5A9p/iBGMg/PGKYUyMD0OAGu8dVT5Lzj8S58g==", + "dev": true, "dependencies": { "cosmiconfig": "^8.0.0", "posthtml": "^0.16.5", @@ -7253,6 +7453,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -7383,12 +7584,14 @@ "node_modules/immutable": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==" + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "dev": true }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -7404,6 +7607,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { "node": ">=4" } @@ -7452,6 +7656,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -7473,12 +7678,14 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -7501,6 +7708,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7527,6 +7735,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -7546,12 +7755,14 @@ "node_modules/is-json": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", - "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==" + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", + "dev": true }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "engines": { "node": ">=0.12.0" } @@ -7589,7 +7800,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/isstream": { "version": "0.1.2", @@ -8260,7 +8472,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -8294,7 +8507,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "node_modules/json-schema": { "version": "0.4.0", @@ -8318,6 +8532,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -8383,6 +8598,7 @@ "version": "1.24.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.24.0.tgz", "integrity": "sha512-y36QEEDVx4IM7/yIZNsZJMRREIu26WzTsauIysf5s76YeCmlSbRZS7aC97IGPuoFRnyZ5Wx43OBsQBFB5Ne7ng==", + "dev": true, "dependencies": { "detect-libc": "^1.0.3" }, @@ -8412,6 +8628,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -8431,6 +8648,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -8450,6 +8668,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -8469,6 +8688,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -8488,6 +8708,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -8507,6 +8728,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -8526,6 +8748,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -8545,6 +8768,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -8564,6 +8788,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -8579,12 +8804,14 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "node_modules/lmdb": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-2.8.5.tgz", "integrity": "sha512-9bMdFfc80S+vSldBmG3HOuLVHnxRdNTlpzR6QDnzqCQtCzGUEAGTzBKYMeIM+I/sU4oZfgbcbS7X7F65/z/oxQ==", + "dev": true, "hasInstallScript": true, "dependencies": { "msgpackr": "^1.9.5", @@ -8608,7 +8835,8 @@ "node_modules/lmdb/node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true }, "node_modules/locate-path": { "version": "5.0.0", @@ -8625,7 +8853,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -8689,6 +8918,7 @@ "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, "optional": true, "peer": true }, @@ -8701,9 +8931,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -8733,6 +8966,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -8805,6 +9039,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8876,6 +9111,7 @@ "version": "1.10.1", "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.1.tgz", "integrity": "sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==", + "dev": true, "optionalDependencies": { "msgpackr-extract": "^3.0.2" } @@ -8884,6 +9120,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz", "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==", + "dev": true, "hasInstallScript": true, "optional": true, "dependencies": { @@ -8905,6 +9142,7 @@ "version": "5.0.7", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==", + "dev": true, "optional": true, "bin": { "node-gyp-build-optional-packages": "bin.js", @@ -8929,7 +9167,8 @@ "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "node_modules/node-abi": { "version": "3.67.0", @@ -8947,6 +9186,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", + "dev": true, "engines": { "node": "^16 || ^18 || >= 20" } @@ -8955,6 +9195,7 @@ "version": "5.0.6", "resolved": "https://registry.npmjs.org/node-elm-compiler/-/node-elm-compiler-5.0.6.tgz", "integrity": "sha512-DWTRQR8b54rvschcZRREdsz7K84lnS8A6YJu8du3QLQ8f204SJbyTaA6NzYYbfUG97OTRKRv/0KZl82cTfpLhA==", + "dev": true, "dependencies": { "cross-spawn": "6.0.5", "find-elm-dependencies": "^2.0.4", @@ -8969,6 +9210,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -8984,6 +9226,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, "engines": { "node": ">=4" } @@ -8992,6 +9235,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, "bin": { "semver": "bin/semver" } @@ -9000,6 +9244,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, "dependencies": { "shebang-regex": "^1.0.0" }, @@ -9011,6 +9256,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -9019,6 +9265,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -9030,6 +9277,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "dev": true, "dependencies": { "detect-libc": "^2.0.1" }, @@ -9043,6 +9291,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "dev": true, "engines": { "node": ">=8" } @@ -9056,7 +9305,8 @@ "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true }, "node_modules/nodemon": { "version": "3.1.4", @@ -9147,6 +9397,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -9194,6 +9445,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -9204,7 +9456,8 @@ "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true }, "node_modules/oauth-sign": { "version": "0.9.0", @@ -9246,6 +9499,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { "wrappy": "1" } @@ -9349,7 +9603,8 @@ "node_modules/ordered-binary": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz", - "integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==" + "integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==", + "dev": true }, "node_modules/p-cancelable": { "version": "2.1.1", @@ -9422,6 +9677,7 @@ "version": "2.12.0", "resolved": "https://registry.npmjs.org/parcel/-/parcel-2.12.0.tgz", "integrity": "sha512-W+gxAq7aQ9dJIg/XLKGcRT0cvnStFAQHPaI0pvD0U2l6IVLueUAm3nwN7lkY62zZNmlvNx6jNtE4wlbS+CyqSg==", + "dev": true, "dependencies": { "@parcel/config-default": "2.12.0", "@parcel/core": "2.12.0", @@ -9453,6 +9709,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, "engines": { "node": ">= 10" } @@ -9461,6 +9718,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -9472,6 +9730,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -9506,6 +9765,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -9514,6 +9774,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -9548,14 +9809,15 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "engines": { "node": ">=8" } @@ -9600,12 +9862,14 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "engines": { "node": ">=8.6" }, @@ -9648,7 +9912,8 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true }, "node_modules/postgres-array": { "version": "2.0.0", @@ -9693,6 +9958,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", + "dev": true, "dependencies": { "posthtml-parser": "^0.11.0", "posthtml-render": "^3.0.0" @@ -9705,6 +9971,7 @@ "version": "0.10.2", "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.10.2.tgz", "integrity": "sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==", + "dev": true, "dependencies": { "htmlparser2": "^7.1.1" }, @@ -9716,6 +9983,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dev": true, "dependencies": { "is-json": "^2.0.1" }, @@ -9727,6 +9995,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, "dependencies": { "htmlparser2": "^7.1.1" }, @@ -9858,11 +10127,11 @@ ] }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -9929,7 +10198,8 @@ "node_modules/react-error-overlay": { "version": "6.0.9", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", - "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" + "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==", + "dev": true }, "node_modules/react-is": { "version": "18.2.0", @@ -9941,6 +10211,7 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.9.0.tgz", "integrity": "sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -9963,6 +10234,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -10354,6 +10626,7 @@ "version": "1.71.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", + "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -10397,9 +10670,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -10419,20 +10692,28 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -10469,6 +10750,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -10480,6 +10762,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } @@ -10553,6 +10836,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -10561,6 +10845,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -10597,6 +10882,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "dev": true, "engines": { "node": ">=12" }, @@ -10639,7 +10925,8 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true }, "node_modules/stack-utils": { "version": "2.0.6", @@ -10861,6 +11148,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -10896,6 +11184,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "dev": true, "optional": true, "peer": true, "dependencies": { @@ -10922,6 +11211,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, "optional": true, "peer": true, "engines": { @@ -10979,6 +11269,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -10991,6 +11282,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, "dependencies": { "minimist": "^1.2.6" }, @@ -11002,6 +11294,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -11013,6 +11306,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, "engines": { "node": ">=8" }, @@ -11040,6 +11334,7 @@ "version": "5.29.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz", "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==", + "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -11056,12 +11351,14 @@ "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true }, "node_modules/terser/node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -11090,7 +11387,8 @@ "node_modules/timsort": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==" + "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", + "dev": true }, "node_modules/tmpl": { "version": "1.0.5", @@ -11111,6 +11409,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -11187,7 +11486,8 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true }, "node_modules/tunnel-agent": { "version": "0.6.0", @@ -11294,6 +11594,7 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -11338,6 +11639,7 @@ "version": "3.11.0", "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "dev": true, "engines": { "node": ">= 4" } @@ -11417,12 +11719,14 @@ "node_modules/weak-lru-cache": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", - "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==" + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -11476,7 +11780,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/package.json b/package.json index 54dec0684..87cafce18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ecobalyse", - "version": "2.1.1", + "version": "2.2.0", "description": "Accélérer l'affichage environnemental de la filière textile française", "author": "Ecobalyse ", "license": "MIT", @@ -16,10 +16,10 @@ "build": "npm run server:build && rimraf dist && npm run build:init && parcel build index.html --public-url ./", "build:init": "./bin/update-version.sh && mkdir -p dist && cp -r public/* dist/ && npm run db:build", "build:standalone-app": "npm run build && npm run server:build && cp server-app.js dist/ && cp openapi.yaml dist/", - "processes:build": "elm make src/ComputeAggregated.elm --output=compute-aggregated-app.js && node compute-aggregated.js", "decrypt": "./bin/decrypt", "encrypt": "./bin/encrypt", - "db:build": "./bin/build-db", + "db:build": "./bin/build-db && npm run db:check", + "db:check": "elm make src/CheckDb.elm --optimize --output=check-db-app.js 1> /dev/null && node check-db.js", "lint:openapi": "npx swagger-cli validate openapi.yaml", "lint:prettier": "prettier --config .prettierrc --check", "lint:prettier:all": "npm run lint:prettier -- .", @@ -34,6 +34,7 @@ "fix:ruff:format": "pipenv run ruff format --force-exclude", "fix:all": "npm run fix:ruff:all && npm run fix:prettier:all", "format:json": "npx prettier@3.0.3 --write . && pipenv run ruff check --select I --fix && pipenv run ruff format", + "processes:build": "elm make src/ComputeAggregated.elm --output=compute-aggregated-app.js 1> /dev/null && node compute-aggregated.js", "server:build": "npm run db:build && elm make src/Server.elm --optimize --output=server-app.js", "server:dev": "npm run server:build && nodemon server.js --config nodemon.json", "server:debug": "elm make src/Server.elm --output=server-app.js && nodemon server.js --config nodemon.json", @@ -50,23 +51,16 @@ "test": "npm run test:client && npm run test:backend && npm run test:server" }, "dependencies": { - "@parcel/transformer-elm": "^2.12.0", - "@parcel/transformer-image": "^2.12.0", - "@parcel/transformer-sass": "^2.12.0", "@sentry/browser": "^8.27.0", "@sentry/node": "^8.27.0", "@sentry/profiling-node": "^8.28.0", "@sentry/tracing": "^7.114.0", - "bootstrap": "^5.3.3", "cors": "^2.8.5", "dotenv": "^16.4.5", - "elm": "^0.19.1-6", - "express": "^4.19.2", + "express": "^4.21.0", "express-rate-limit": "^7.4.0", "helmet": "^7.1.0", - "highcharts": "^11.4.8", "js-yaml": "^4.1.0", - "parcel": "^2.12.0", "piwik": "^1.0.9" }, "devDependencies": { @@ -82,10 +76,18 @@ "prettier": "^3.3.3", "process": "^0.11.10", "rimraf": "^6.0.1", - "supertest": "^7.0.0" + "supertest": "^7.0.0", + "parcel": "^2.12.0", + "@parcel/transformer-elm": "^2.12.0", + "@parcel/transformer-image": "^2.12.0", + "@parcel/transformer-sass": "^2.12.0", + "highcharts": "^11.4.8", + "bootstrap": "^5.3.3", + "elm": "^0.19.1-6" }, "cacheDirectories": [ "node_modules", - "~/.elm" + "elm-stuff", + ".elm" ] } diff --git a/packages/python/ecobalyse/ecobalyse/github.py b/packages/python/ecobalyse/ecobalyse/github.py index 1c392e031..8c178e69f 100644 --- a/packages/python/ecobalyse/ecobalyse/github.py +++ b/packages/python/ecobalyse/ecobalyse/github.py @@ -14,7 +14,7 @@ def extract_branch_name(content: str) -> str | None: # (it should be part of the body) # Branch names format: https://docs.github.com/en/get-started/using-git/dealing-with-special-characters-in-branch-and-tag-names#naming-branches-and-tags # The English alphabet (a to z and A to Z), Numbers (0 to 9), period (.), hyphen (-), underscore (_), forward slash (/) - result = re.search(r"ecobalyse_data: ([0-9a-zA-Z./_-]+)", content, re.M | re.I) + result = re.search(r"ecobalyse-private: ([0-9a-zA-Z./_-]+)", content, re.M | re.I) if result: return result.group(1) diff --git a/tests/test_data_branch.py b/packages/python/ecobalyse/ecobalyse/tests/test_data_branch.py similarity index 59% rename from tests/test_data_branch.py rename to packages/python/ecobalyse/ecobalyse/tests/test_data_branch.py index 3b979cb50..8a1f2efab 100644 --- a/tests/test_data_branch.py +++ b/packages/python/ecobalyse/ecobalyse/tests/test_data_branch.py @@ -2,25 +2,27 @@ def test_extract_data_branch_name(client): - branch_name = extract_branch_name("ecobalyse_data: test_branch_name") + branch_name = extract_branch_name("ecobalyse-private: test_branch_name") assert branch_name == "test_branch_name" - branch_name = extract_branch_name("nstnsnecobalyse_data: test_branch_name nstnstn") + branch_name = extract_branch_name( + "nstnsnecobalyse-private: test_branch_name nstnstn" + ) assert branch_name == "test_branch_name" branch_name = extract_branch_name("""tnsrtntntsr - nstnsnecobalyse_data: test_branch_name nstnstn + nstnsnecobalyse-private: test_branch_name nstnstn eaiuiuea""") assert branch_name == "test_branch_name" branch_name = extract_branch_name( - "nstnsnecobalyse_data: test_branch_name/9./_- nstnstn" + "nstnsnecobalyse-private: test_branch_name/9./_- nstnstn" ) assert branch_name == "test_branch_name/9./_-" # We should not include invalid characters in the match branch_name = extract_branch_name( - "nstnsnecobalyse_data: test_bran Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -38,6 +39,7 @@ }, { "name": "market group for electricity, medium voltage, RAF", + "displayName": "Électricité moyenne tension, Afrique", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -75,6 +77,7 @@ }, { "name": "market group for electricity, medium voltage, RME", + "displayName": "Électricité moyenne tension, Moyen-Orient", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -112,6 +115,7 @@ }, { "name": "market group for electricity, medium voltage, RLA", + "displayName": "Électricité moyenne tension, Amérique latine", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -149,6 +153,7 @@ }, { "name": "market group for electricity, medium voltage, RNA", + "displayName": "Électricité moyenne tension, Amérique du nord", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -186,6 +191,7 @@ }, { "name": "market for electricity, medium voltage, AU", + "displayName": "Électricité moyenne tension, Australie", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -223,6 +229,7 @@ }, { "name": "market group for electricity, medium voltage, CN", + "displayName": "Électricité moyenne tension, Chine", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -260,6 +267,7 @@ }, { "name": "market for electricity, medium voltage, AL", + "displayName": "Électricité moyenne tension, Albanie", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -297,6 +305,7 @@ }, { "name": "market for electricity, medium voltage, PE", + "displayName": "Électricité moyenne tension, Pérou", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -334,6 +343,7 @@ }, { "name": "market for electricity, medium voltage, NZ", + "displayName": "Électricité moyenne tension, Nouvelle-Zélande", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -371,6 +381,7 @@ }, { "name": "market for electricity, medium voltage, MA", + "displayName": "Électricité moyenne tension, Maroc", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -408,6 +419,7 @@ }, { "name": "market for electricity, medium voltage, KE", + "displayName": "Électricité moyenne tension, Kenya", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -445,6 +457,7 @@ }, { "name": "market for electricity, medium voltage, IT", + "displayName": "Électricité moyenne tension, Italie", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -482,6 +495,7 @@ }, { "name": "market for electricity, medium voltage, RER", + "displayName": "Électricité moyenne tension, Europe", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -519,6 +533,7 @@ }, { "name": "market for electricity, medium voltage, ES", + "displayName": "Électricité moyenne tension, Espagne", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -556,6 +571,7 @@ }, { "name": "market for electricity, medium voltage, FR", + "displayName": "Électricité moyenne tension, France", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -593,6 +609,7 @@ }, { "name": "market group for electricity, medium voltage, MM", + "displayName": "Électricité moyenne tension, Myanmar", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -630,6 +647,7 @@ }, { "name": "market group for electricity, medium voltage, IN", + "displayName": "Électricité moyenne tension, Inde", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -667,6 +685,7 @@ }, { "name": "Elasthane (Lycra)", + "displayName": "Elasthane (Lycra)", "info": "Textile > Matières > Matières synthétiques", "unit": "kg", "source": "Ecobalyse", @@ -704,6 +723,7 @@ }, { "name": "polymethyl methacrylate production, beads [RoW]", + "displayName": "Production de plexiglas (Polyméthacrylate de méthyle)", "info": "Textile > Matières > Matières synthétiques", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -741,6 +761,7 @@ }, { "name": "fibre production, jute, retting [RoW]", + "displayName": "Production de jute, rouissage", "info": "Textile > Matières > Matières naturelles", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -778,6 +799,7 @@ }, { "name": "polypropylene production, granulate [RoW]", + "displayName": "Production de polypropylène, granulés", "info": "Textile > Matières > Matières synthétiques", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -815,6 +837,7 @@ }, { "name": "polyethylene terephthalate production, granulate, amorphous [RoW]", + "displayName": "Production de PET, granulés, amorphe", "info": "Textile > Matières > Matières synthétiques", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -852,6 +875,7 @@ }, { "name": "polyethylene terephthalate production, granulate, amorphous, recycled [RoW]", + "displayName": "Production de PET recyclé, granulés, amorphe", "info": "Textile > Matières > Matières synthétiques", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -889,6 +913,7 @@ }, { "name": "nylon 6-6 production [RoW]", + "displayName": "Production de nylon 6-6", "info": "Textile > Matières > Matières synthétiques", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -924,45 +949,9 @@ "waste": 0.0319569, "alias": null }, - { - "name": "viscose fibre, recycled, post-consumer, production [GLO]", - "info": "Textile > Matières > Matières recyclées", - "unit": "kg", - "source": "Ecoinvent 3.9.1", - "correctif": "", - "step_usage": "Matières", - "uuid": "7e2fdd1e285fd01e8852977a584a9ae3", - "impacts": { - "acd": 0, - "cch": 0, - "etf": 0, - "etf-c": 0, - "fru": 0, - "fwe": 0, - "htc": 0, - "htc-c": 0, - "htn": 0, - "htn-c": 0, - "ior": 0, - "ldu": 0, - "mru": 0, - "ozd": 0, - "pco": 0, - "pma": 0, - "swe": 0, - "tre": 0, - "wtu": 0, - "ecs": 61.70583196748168, - "pef": 66.11022949135275 - }, - "heat_MJ": 0, - "elec_pppm": 0, - "elec_MJ": 0, - "waste": 0.137851, - "alias": null - }, { "name": "fibre production, flax, retting [RoW]", + "displayName": "Production de fibres de lin, rouissage", "info": "Textile > Matières > Matières naturelles", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -1000,6 +989,7 @@ }, { "name": "Laine par défaut", + "displayName": "Laine par défaut", "info": "Textile > Matières > Matières naturelles", "unit": "kg", "source": "Ecobalyse", @@ -1037,6 +1027,7 @@ }, { "name": "Laine nouvelle filière", + "displayName": "Laine nouvelle filière", "info": "Textile > Matières > Matières naturelles uuid_bi=376bd165-d354-41aa-a6e3-fd3228413bb2", "unit": "kg", "source": "Ecobalyse", @@ -1074,6 +1065,7 @@ }, { "name": "fibre production, cotton, ginning, RoW", + "displayName": "Production de fibres de coton", "info": "Textile > Matières > Matières naturelles", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -1111,6 +1103,7 @@ }, { "name": "fibre production, cotton, organic, ginning [RoW] (Ecobalyse)", + "displayName": "Production de fibres de coton bio", "info": "Textile > Matières > Matières naturelles", "unit": "kg", "source": "Ecobalyse", @@ -1148,6 +1141,7 @@ }, { "name": "sunn hemp production [RoW]", + "displayName": "Production de chanvre", "info": "Textile > Matières > Matières naturelles", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -1185,6 +1179,7 @@ }, { "name": "market for fibre, viscose [GLO]", + "displayName": "Fibre de viscose", "info": "Textile > Matières > Matières synthétiques", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -1222,6 +1217,7 @@ }, { "name": "Production de coton recyclé (recyclage mécanique) pré-filature, traitement de déchets textiles post-consommation, inventaire partiellement agrégé", + "displayName": "Production de coton recyclé (déchets post-consommation)", "info": "Textile > Matières > Matières recyclées uuid_bi=4d23093d-1346-4018-8c0f-7aae33c67bcd", "unit": "kg", "source": "Ecobalyse", @@ -1259,6 +1255,7 @@ }, { "name": "Production de coton recyclé (recyclage mécanique) pré-filature, traitement de déchets de production textiles, inventaire partiellement agrégé", + "displayName": "Production de coton recyclé (déchets de production)", "info": "Textile > Matières > Matières recyclées uuid_bi=2b24abb0-c1ec-4298-9b58-350904a26104", "unit": "kg", "source": "Ecobalyse", @@ -1296,6 +1293,7 @@ }, { "name": "Tricotage moyen (mix de métiers circulaire & rectiligne)", + "displayName": "Tricotage moyen (mix de métiers circulaire & rectiligne)", "info": "Textile > Mise en forme > Tricotage", "unit": "kg", "source": "Ecobalyse", @@ -1333,6 +1331,7 @@ }, { "name": "Tricotage fully-fashioned", + "displayName": "Tricotage fully-fashioned", "info": "Textile > Mise en forme > Tricotage", "unit": "kg", "source": "Ecobalyse", @@ -1370,6 +1369,7 @@ }, { "name": "Tricotage seamless", + "displayName": "Tricotage seamless", "info": "Textile > Mise en forme > Tricotage", "unit": "kg", "source": "Ecobalyse", @@ -1407,6 +1407,7 @@ }, { "name": "Tissage (habillement)", + "displayName": "Tissage (habillement)", "info": "Textile > Mise en forme > Tissage", "unit": "kg", "source": "Ecobalyse", @@ -1444,6 +1445,7 @@ }, { "name": "Teinture sur étoffe", + "displayName": "Teinture sur étoffe", "info": "Textile > Ennoblissement > Teinture", "unit": "kg", "source": "Base Impacts 2.01", @@ -1481,6 +1483,7 @@ }, { "name": "Teinture sur pièce", + "displayName": "Teinture sur pièce", "info": "Textile > Ennoblissement > Teinture", "unit": "kg", "source": "Base Impacts 2.01", @@ -1518,6 +1521,7 @@ }, { "name": "Teinture sur fil", + "displayName": "Teinture sur fil", "info": "Textile > Ennoblissement > Teinture", "unit": "kg", "source": "Base Impacts 2.01", @@ -1555,6 +1559,7 @@ }, { "name": "transport, freight, sea, container ship//[GLO] market for transport, freight, sea, container ship", + "displayName": "transport maritime", "info": "Transport > Maritime > Flotte moyenne", "unit": "t*km", "source": "Ecoinvent 3.9.1", @@ -1592,6 +1597,7 @@ }, { "name": "transport, freight, aircraft, long haul//[GLO] market for transport, freight, aircraft, long haul", + "displayName": "transport aérien long-courrier", "info": "Transport > Aérien > Flotte moyenne", "unit": "t*km", "source": "Ecoinvent 3.9.1", @@ -1629,6 +1635,7 @@ }, { "name": "transport, freight train//[GLO] market group for transport, freight train", + "displayName": "transport ferroviaire", "info": "Transport > Train > Flotte moyenne", "unit": "t*km", "source": "Ecoinvent 3.9.1", @@ -1666,6 +1673,7 @@ }, { "name": "transport, freight, lorry, unspecified//[GLO] market group for transport, freight, lorry, unspecified", + "displayName": "transport routier", "info": "Transport > Routier > Flotte moyenne continentale", "unit": "t*km", "source": "Ecoinvent 3.9.1", @@ -1703,6 +1711,7 @@ }, { "name": "Transport en camion non spécifié France (dont parc, utilisation et infrastructure) (50%) [tkm], FR", + "displayName": "Transport en camion non spécifié France", "info": "Transport > Routier > Flotte moyenne française", "unit": "t*km", "source": "Base Impacts 2.01", @@ -1740,6 +1749,7 @@ }, { "name": "market for electricity, medium voltage, TN", + "displayName": "Électricité moyenne tension, Tunisie", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -1777,6 +1787,7 @@ }, { "name": "market for electricity, medium voltage, TR", + "displayName": "Électricité moyenne tension, Turquie", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -1814,6 +1825,7 @@ }, { "name": "market for electricity, medium voltage, BD", + "displayName": "Électricité moyenne tension, Bangladesh", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -1851,6 +1863,7 @@ }, { "name": "market group for electricity, medium voltage, BR", + "displayName": "Électricité moyenne tension, Brésil", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -1888,6 +1901,7 @@ }, { "name": "market for electricity, medium voltage, CZ", + "displayName": "Électricité moyenne tension, République Tchèque", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -1925,6 +1939,7 @@ }, { "name": "market for electricity, medium voltage, ET", + "displayName": "Électricité moyenne tension, Éthiopie", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -1962,6 +1977,7 @@ }, { "name": "market for electricity, medium voltage, KH", + "displayName": "Électricité moyenne tension, Cambodge", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -1999,6 +2015,7 @@ }, { "name": "market for electricity, medium voltage, LK", + "displayName": "Électricité moyenne tension, Sri Lanka", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -2036,6 +2053,7 @@ }, { "name": "market for electricity, medium voltage, PK", + "displayName": "Électricité moyenne tension, Pakistan", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -2073,6 +2091,7 @@ }, { "name": "market group for electricity, medium voltage US", + "displayName": "Électricité moyenne tension, USA", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -2110,6 +2129,7 @@ }, { "name": "market for electricity, medium voltage, VN", + "displayName": "Électricité moyenne tension, Viet Nam", "info": "Energie > Electricité > Mix moyen", "unit": "kWh", "source": "Ecoinvent 3.9.1", @@ -2147,10 +2167,11 @@ }, { "name": "Heat mix (Europe)", + "displayName": "Mix chaleur (Europe)", "info": "Energie > Chaleur > Vapeur par énergie primaire", "unit": "MJ", - "source": "Ecoinvent 3.9.1", - "correctif": "Reconstitution du mix", + "source": "Ecobalyse", + "correctif": "Reconstitution du mix depuis Ecoinvent 3.9.1", "step_usage": "Energie", "uuid": "heat-europe", "impacts": { @@ -2184,10 +2205,11 @@ }, { "name": "Heat mix (World)", + "displayName": "Mix chaleur (Monde)", "info": "Energie > Chaleur > Vapeur par énergie primaire", "unit": "MJ", - "source": "Ecoinvent 3.9.1", - "correctif": "Reconstitution du mix", + "source": "Ecobalyse", + "correctif": "Reconstitution du mix depuis Ecoinvent 3.9.1", "step_usage": "Energie", "uuid": "heat-row", "impacts": { @@ -2221,6 +2243,7 @@ }, { "name": "Utilisation : Impact hors repassage (électricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées) (Chemisier) - Non ironing impact", + "displayName": "Utilisation : Impact hors repassage (Chemisier)", "info": "Utilisation > Aggrégation multi-impacts > Electricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées", "unit": "kg", "source": "Ecobalyse", @@ -2258,6 +2281,7 @@ }, { "name": "Utilisation : Impact hors repassage (électricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées) (Jean) - Non ironing impact", + "displayName": "Utilisation : Impact hors repassage (Jean)", "info": "Utilisation > Aggrégation multi-impacts > Electricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées", "unit": "kg", "source": "Ecobalyse", @@ -2295,6 +2319,7 @@ }, { "name": "Utilisation : Impact hors repassage (électricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées) (Jupe) - Non ironing impact", + "displayName": "Utilisation : Impact hors repassage (Jupe)", "info": "Utilisation > Aggrégation multi-impacts > Electricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées", "unit": "kg", "source": "Ecobalyse", @@ -2332,6 +2357,7 @@ }, { "name": "Utilisation : Impact hors repassage (électricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées) (Manteau) - Non ironing impact", + "displayName": "Utilisation : Impact hors repassage (Manteau)", "info": "Utilisation > Aggrégation multi-impacts > Electricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées", "unit": "kg", "source": "Ecobalyse", @@ -2369,6 +2395,7 @@ }, { "name": "Utilisation : Impact hors repassage (électricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées) (Pantalon) - Non ironing impact", + "displayName": "Utilisation : Impact hors repassage (Pantalon)", "info": "Utilisation > Aggrégation multi-impacts > Electricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées", "unit": "kg", "source": "Ecobalyse", @@ -2406,6 +2433,7 @@ }, { "name": "Utilisation : Impact hors repassage (électricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées) (Pull) - Non ironing impact", + "displayName": "Utilisation : Impact hors repassage (Pull)", "info": "Utilisation > Aggrégation multi-impacts > Electricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées", "unit": "kg", "source": "Ecobalyse", @@ -2443,6 +2471,7 @@ }, { "name": "Utilisation : Impact hors repassage (électricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées) (Robe) - Non ironing impact", + "displayName": "Utilisation : Impact hors repassage (Robe)", "info": "Utilisation > Aggrégation multi-impacts > Electricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées", "unit": "kg", "source": "Ecobalyse", @@ -2480,6 +2509,7 @@ }, { "name": "Utilisation : Impact hors repassage (électricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées) (T-shirt) - Non ironing impact", + "displayName": "Utilisation : Impact hors repassage (T-shirt)", "info": "Utilisation > Aggrégation multi-impacts > Electricité (lave-linge, sèche-linge), lessive liquide et traitement des eaux usées", "unit": "kg", "source": "Ecobalyse", @@ -2517,6 +2547,7 @@ }, { "name": "Transport en voiture jusqu'au point de collecte précalculé pour la fin de vie", + "displayName": "Transport en voiture jusqu'au point de collecte précalculé pour la fin de vie", "info": "Transport > Routier > Voiture individuelle", "unit": "Item(s)", "source": "Base Impacts 2.01", @@ -2554,6 +2585,7 @@ }, { "name": "Mise en décharge de textiles, FR", + "displayName": "Mise en décharge de textiles, FR", "info": "Traitement de fin de vie > Mise en décharge > Fractions de déchets", "unit": "kg", "source": "Ecobalyse", @@ -2591,6 +2623,7 @@ }, { "name": "Fin de vie hors voiture (transport en camion, incinération, mise en décharge)", + "displayName": "Fin de vie hors voiture (transport en camion, incinération, mise en décharge)", "info": "Fin de vie > Aggrégation multi-impacts > ", "unit": "kg", "source": "Ecobalyse", @@ -2628,6 +2661,7 @@ }, { "name": "Tricotage rectiligne, inventaire désagrégé", + "displayName": "Tricotage rectiligne", "info": "Textile > Mise en forme > Tricotage", "unit": "kg", "source": "Ecobalyse", @@ -2665,6 +2699,7 @@ }, { "name": "Tricotage circulaire, inventaire désagrégé", + "displayName": "Tricotage circulaire", "info": "Textile > Mise en forme > Tricotage", "unit": "kg", "source": "Ecobalyse", @@ -2702,6 +2737,7 @@ }, { "name": "Délavage chimique, procédé majorant, traitement inefficace des eaux usées", + "displayName": "Délavage chimique, procédé majorant, traitement inefficace des eaux usées", "info": "Textile > Ennoblissement > Delavage", "unit": "kg", "source": "Base Impacts 2.01", @@ -2739,6 +2775,7 @@ }, { "name": "Impression pigmentaire, procédé représentatif, traitement moyen des eaux usées", + "displayName": "Impression pigmentaire, procédé représentatif, traitement moyen des eaux usées", "info": "Textile > Ennoblissement > Impression", "unit": "m2", "source": "Base Impacts 2.01", @@ -2776,6 +2813,7 @@ }, { "name": "Impression fixé-lavé, procédé représentatif, traitement moyen des eaux usées", + "displayName": "Impression fixé-lavé, procédé représentatif, traitement moyen des eaux usées", "info": "Textile > Ennoblissement > Impression", "unit": "m2", "source": "Base Impacts 2.01", @@ -2813,6 +2851,7 @@ }, { "name": "Apprêt anti-tache, procédé représentatif, traitement moyen des eaux usées", + "displayName": "Apprêt anti-tache, procédé représentatif, traitement moyen des eaux usées", "info": "Textile > Ennoblissement > Appret chimique", "unit": "kg", "source": "Base Impacts 2.01", @@ -2850,6 +2889,7 @@ }, { "name": "Teinture fibres synthétiques", + "displayName": "Teinture fibres synthétiques", "info": "Textile > Ennoblissement > Teinture", "unit": "kg", "source": "Ecobalyse", @@ -2887,6 +2927,7 @@ }, { "name": "Teinture fibres cellulosiques", + "displayName": "Teinture fibres cellulosiques", "info": "Textile > Ennoblissement > Teinture", "unit": "kg", "source": "Ecobalyse", @@ -2924,6 +2965,7 @@ }, { "name": "Blanchiment", + "displayName": "Blanchiment", "info": "Textile > Ennoblissement > Blanchiment", "unit": "kg", "source": "Ecoinvent 3.9.1", @@ -2961,6 +3003,7 @@ }, { "name": "Impression (pigmentaire)", + "displayName": "Impression (pigmentaire)", "info": "Textile > Ennoblissement > Impression", "unit": "kg", "source": "Ecobalyse", @@ -2998,6 +3041,7 @@ }, { "name": "Impression fixé-lavé (colorants)", + "displayName": "Impression fixé-lavé (colorants)", "info": "Textile > Ennoblissement > Impression", "unit": "kg", "source": "Ecobalyse", diff --git a/review/elm.json b/review/elm.json index c7734475a..ba8761219 100644 --- a/review/elm.json +++ b/review/elm.json @@ -1,10 +1,13 @@ { "type": "application", - "source-directories": ["src"], + "source-directories": [ + "src" + ], "elm-version": "0.19.1", "dependencies": { "direct": { "elm/core": "1.0.5", + "fysiweb/elm-review-sorted": "1.0.3", "jfmengels/elm-review": "2.14.0", "jfmengels/elm-review-cognitive-complexity": "1.0.3", "jfmengels/elm-review-common": "1.3.3", diff --git a/review/src/ReviewConfig.elm b/review/src/ReviewConfig.elm index abaa9157a..c94bc70e3 100644 --- a/review/src/ReviewConfig.elm +++ b/review/src/ReviewConfig.elm @@ -18,7 +18,8 @@ import NoUnused.Variables import Review.Rule as Rule exposing (Rule) import Simplify import CognitiveComplexity - +import NoUnsortedRecordFields +import NoUnsortedConstructors config : List Rule config = @@ -44,6 +45,15 @@ config = |> Rule.ignoreErrorsForDirectories [ "tests/" ] , NoRedundantConcat.rule , NoRedundantCons.rule + , NoUnsortedRecordFields.rule + |> Rule.ignoreErrorsForDirectories [ "tests/" ] + |> Rule.ignoreErrorsForDirectories [ "src/Page" ] + |> Rule.ignoreErrorsForFiles [ "src/Data/Impact/Definition.elm" ] + |> Rule.ignoreErrorsForFiles [ "src/Views/Page.elm" ] + , NoUnsortedConstructors.rule + |> Rule.ignoreErrorsForDirectories [ "tests/" ] + |> Rule.ignoreErrorsForFiles [ "src/Data/Impact/Definition.elm" ] + |> Rule.ignoreErrorsForFiles [ "src/Data/Impact/Definition.elm" ] -- NoUnused , NoUnused.CustomTypeConstructors.rule [] |> Rule.ignoreErrorsForFiles [ "src/Views/Modal.elm" ] diff --git a/scalingo.json b/scalingo.json new file mode 100644 index 000000000..2f8b9b2ae --- /dev/null +++ b/scalingo.json @@ -0,0 +1,7 @@ +{ + "env": { + "IS_REVIEW_APP": { + "value": "true" + } + } +} diff --git a/server.js b/server.js index a33311666..979f0e9fb 100644 --- a/server.js +++ b/server.js @@ -21,7 +21,14 @@ const djangoPort = 8002; const version = express(); // version app // Env vars -const { ECOBALYSE_DATA_DIR, MATOMO_HOST, MATOMO_SITE_ID, MATOMO_TOKEN, NODE_ENV } = process.env; +const { + ECOBALYSE_DATA_DIR, + ENABLE_FOOD_SECTION, + MATOMO_HOST, + MATOMO_SITE_ID, + MATOMO_TOKEN, + NODE_ENV, +} = process.env; var rateLimiter = rateLimit({ windowMs: 1000, // 1 second @@ -155,8 +162,10 @@ if (fs.existsSync(versionsDir)) { // API -const openApiContents = yaml.load(fs.readFileSync("openapi.yaml")); -openApiContents.version = require("./package.json").version; +const openApiContents = processOpenApi( + yaml.load(fs.readFileSync("openapi.yaml")), + require("./package.json").version, +); // Matomo const apiTracker = lib.setupTracker(openApiContents); @@ -186,6 +195,18 @@ const getProcesses = async (token, customProcessesImpacts, customProcesses) => { } }; +function processOpenApi(contents, versionNumber) { + // Add app version info to openapi docs + contents.version = versionNumber; + // Remove food api docs if disabled from env + if (ENABLE_FOOD_SECTION !== "True") { + contents.paths = Object.fromEntries( + Object.entries(contents.paths).filter(([path, _]) => !path.startsWith("/food")), + ); + } + return contents; +} + app.get("/processes/processes.json", async (req, res) => { return res.status(200).send(await getProcesses(req.headers.token)); }); @@ -243,7 +264,10 @@ version.use("/:versionNumber", checkVersionAndPath, (req, res, next) => { }); version.get("/:versionNumber/api", checkVersionAndPath, (req, res) => { - const openApiContents = yaml.load(fs.readFileSync(path.join(req.staticDir, "openapi.yaml"))); + const openApiContents = processOpenApi( + yaml.load(fs.readFileSync(path.join(req.staticDir, "openapi.yaml"))), + req.params.versionNumber, + ); res.status(200).send(openApiContents); }); diff --git a/src/CheckDb.elm b/src/CheckDb.elm new file mode 100644 index 000000000..2d98b455d --- /dev/null +++ b/src/CheckDb.elm @@ -0,0 +1,45 @@ +port module CheckDb exposing (main) + +import Static.Db as StaticDb exposing (Db) +import Static.Json as StaticJson + + +type alias Flags = + { foodProcesses : String + , textileProcesses : String + } + + +init : Flags -> ( (), Cmd () ) +init flags = + ( () + , case checkStaticDatabases flags of + Err error -> + logAndExit { message = error, status = 1 } + + Ok _ -> + logAndExit { message = "Dbs look fine", status = 0 } + ) + + +checkStaticDatabases : Flags -> Result String ( Db, Db ) +checkStaticDatabases detailedRawJsonProcesses = + Result.map2 Tuple.pair + (StaticDb.db StaticJson.rawJsonProcesses + |> Result.mapError (\err -> "Non-detailed Db is invalid: " ++ err) + ) + (StaticDb.db detailedRawJsonProcesses + |> Result.mapError (\err -> "Detailed Db is invalid: " ++ err) + ) + + +main : Program Flags () () +main = + Platform.worker + { init = init + , subscriptions = always Sub.none + , update = \_ _ -> ( (), Cmd.none ) + } + + +port logAndExit : { message : String, status : Int } -> Cmd msg diff --git a/src/ComputeAggregated.elm b/src/ComputeAggregated.elm index 04b88dccd..57128635a 100644 --- a/src/ComputeAggregated.elm +++ b/src/ComputeAggregated.elm @@ -11,8 +11,8 @@ import Quantity type alias Flags = { definitionsString : String - , textileProcessesString : String , foodProcessesString : String + , textileProcessesString : String } @@ -49,7 +49,7 @@ keepOnlyAggregated processes = toExport : Flags -> Result Decode.Error Encode.Value -toExport { definitionsString, textileProcessesString, foodProcessesString } = +toExport { definitionsString, foodProcessesString, textileProcessesString } = definitionsString |> Decode.decodeString Definition.decode |> Result.andThen @@ -87,11 +87,6 @@ toExport { definitionsString, textileProcessesString, foodProcessesString } = init : Flags -> ( (), Cmd () ) init flags = case toExport flags of - Ok encodedValue -> - ( () - , export encodedValue - ) - Err error -> ( () , error @@ -100,13 +95,18 @@ init flags = |> logError ) + Ok encodedValue -> + ( () + , export encodedValue + ) + main : Program Flags () () main = Platform.worker { init = init - , update = \_ _ -> ( (), Cmd.none ) , subscriptions = always Sub.none + , update = \_ _ -> ( (), Cmd.none ) } diff --git a/src/Data/AutocompleteSelector.elm b/src/Data/AutocompleteSelector.elm index b691eec58..a14037e1c 100644 --- a/src/Data/AutocompleteSelector.elm +++ b/src/Data/AutocompleteSelector.elm @@ -8,9 +8,9 @@ import Task init : (element -> String) -> List element -> Autocomplete element init toString availableElements = Autocomplete.init - { query = "" - , choices = availableElements + { choices = availableElements , ignoreList = [] + , query = "" } (\lastChoices -> Task.succeed diff --git a/src/Data/Bookmark.elm b/src/Data/Bookmark.elm index 860bbbed9..a6f809f37 100644 --- a/src/Data/Bookmark.elm +++ b/src/Data/Bookmark.elm @@ -24,8 +24,8 @@ import Time exposing (Posix) type alias Bookmark = - { name : String - , created : Posix + { created : Posix + , name : String , query : Query } @@ -38,8 +38,8 @@ type Query decode : Decoder Bookmark decode = Decode.map3 Bookmark - (Decode.field "name" Decode.string) (Decode.field "created" (Decode.map Time.millisToPosix Decode.int)) + (Decode.field "name" Decode.string) (Decode.field "query" decodeQuery) @@ -54,8 +54,8 @@ decodeQuery = encode : Bookmark -> Encode.Value encode v = Encode.object - [ ( "name", Encode.string v.name ) - , ( "created", Encode.int <| Time.posixToMillis v.created ) + [ ( "created", Encode.int <| Time.posixToMillis v.created ) + , ( "name", Encode.string v.name ) , ( "query", encodeQuery v.query ) ] diff --git a/src/Data/Country.elm b/src/Data/Country.elm index 8a8b48c88..a8b649ba6 100644 --- a/src/Data/Country.elm +++ b/src/Data/Country.elm @@ -28,20 +28,20 @@ type Code type AquaticPollutionScenario - = Best - | Average + = Average + | Best | Worst type alias Country = - { code : Code - , name : String - , zone : Zone + { airTransportRatio : Split + , aquaticPollutionScenario : AquaticPollutionScenario + , code : Code , electricityProcess : Process , heatProcess : Process - , airTransportRatio : Split + , name : String , scopes : List Scope - , aquaticPollutionScenario : AquaticPollutionScenario + , zone : Zone } @@ -65,14 +65,14 @@ 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 "name" Decode.string - |> Pipe.required "zone" Zone.decode |> Pipe.required "electricityProcessUuid" (Process.decodeFromUuid processes) |> Pipe.required "heatProcessUuid" (Process.decodeFromUuid processes) - |> Pipe.required "airTransportRatio" Split.decodeFloat + |> Pipe.required "name" Decode.string |> Pipe.optional "scopes" (Decode.list Scope.decode) [ Scope.Food, Scope.Textile ] - |> Pipe.required "aquaticPollutionScenario" decodeAquaticPollutionScenario + |> Pipe.required "zone" Zone.decode decodeCode : Decoder Code @@ -88,13 +88,13 @@ decodeList processes = encode : Country -> Encode.Value encode v = Encode.object - [ ( "code", encodeCode v.code ) - , ( "name", Encode.string v.name ) + [ ( "airTransportRatio", Split.encodeFloat v.airTransportRatio ) + , ( "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 ) - , ( "airTransportRatio", Split.encodeFloat v.airTransportRatio ) + , ( "name", Encode.string v.name ) , ( "scopes", v.scopes |> Encode.list Scope.encode ) - , ( "aquaticPollutionScenario", v.aquaticPollutionScenario |> aquaticPollutionScenarioToString |> Encode.string ) ] @@ -113,12 +113,12 @@ decodeAquaticPollutionScenario = aquaticPollutionScenarioFromString : String -> Result String AquaticPollutionScenario aquaticPollutionScenarioFromString string = case string of - "Best" -> - Ok Best - "Average" -> Ok Average + "Best" -> + Ok Best + "Worst" -> Ok Worst @@ -129,12 +129,12 @@ aquaticPollutionScenarioFromString string = aquaticPollutionScenarioToString : AquaticPollutionScenario -> String aquaticPollutionScenarioToString scenario = case scenario of - Best -> - "Best" - Average -> "Average" + Best -> + "Best" + Worst -> "Worst" @@ -142,12 +142,12 @@ aquaticPollutionScenarioToString scenario = getAquaticPollutionRatio : AquaticPollutionScenario -> Split getAquaticPollutionRatio scenario = case scenario of - Best -> - Split.tenth - Average -> Split.fromPercent 36 |> Result.withDefault Split.full + Best -> + Split.tenth + Worst -> Split.fromPercent 65 |> Result.withDefault Split.full diff --git a/src/Data/Dataset.elm b/src/Data/Dataset.elm index d916b7594..518a1efe5 100644 --- a/src/Data/Dataset.elm +++ b/src/Data/Dataset.elm @@ -29,14 +29,14 @@ It's used by Page.Explore and related routes. -} type Dataset = Countries (Maybe Country.Code) - | Impacts (Maybe Definition.Trigram) | FoodExamples (Maybe Uuid) | FoodIngredients (Maybe Ingredient.Id) | FoodProcesses (Maybe FoodProcess.Identifier) + | Impacts (Maybe Definition.Trigram) | TextileExamples (Maybe Uuid) - | TextileProducts (Maybe Product.Id) | TextileMaterials (Maybe Material.Id) | TextileProcesses (Maybe Process.Uuid) + | TextileProducts (Maybe Product.Id) datasets : Scope -> List Dataset @@ -66,20 +66,17 @@ fromSlug string = "countries" -> Countries Nothing - "impacts" -> - Impacts Nothing - "food-examples" -> FoodExamples Nothing - "ingredients" -> - FoodIngredients Nothing - "food-processes" -> FoodProcesses Nothing - "products" -> - TextileProducts Nothing + "impacts" -> + Impacts Nothing + + "ingredients" -> + FoodIngredients Nothing "materials" -> TextileMaterials Nothing @@ -87,6 +84,9 @@ fromSlug string = "processes" -> TextileProcesses Nothing + "products" -> + TextileProducts Nothing + _ -> TextileExamples Nothing @@ -97,9 +97,6 @@ isDetailed dataset = Countries (Just _) -> True - Impacts (Just _) -> - True - FoodExamples (Just _) -> True @@ -109,10 +106,10 @@ isDetailed dataset = FoodProcesses (Just _) -> True - TextileExamples (Just _) -> + Impacts (Just _) -> True - TextileProducts (Just _) -> + TextileExamples (Just _) -> True TextileMaterials (Just _) -> @@ -121,6 +118,9 @@ isDetailed dataset = TextileProcesses (Just _) -> True + TextileProducts (Just _) -> + True + _ -> False @@ -141,9 +141,6 @@ reset dataset = Countries _ -> Countries Nothing - Impacts _ -> - Impacts Nothing - FoodExamples _ -> FoodExamples Nothing @@ -153,18 +150,21 @@ reset dataset = FoodProcesses _ -> FoodProcesses Nothing + Impacts _ -> + Impacts Nothing + TextileExamples _ -> TextileExamples Nothing - TextileProducts _ -> - TextileProducts Nothing - TextileMaterials _ -> TextileMaterials Nothing TextileProcesses _ -> TextileProcesses Nothing + TextileProducts _ -> + TextileProducts Nothing + same : Dataset -> Dataset -> Bool same a b = @@ -172,9 +172,6 @@ same a b = ( Countries _, Countries _ ) -> True - ( Impacts _, Impacts _ ) -> - True - ( FoodExamples _, FoodExamples _ ) -> True @@ -184,10 +181,10 @@ same a b = ( FoodProcesses _, FoodProcesses _ ) -> True - ( TextileExamples _, TextileExamples _ ) -> + ( Impacts _, Impacts _ ) -> True - ( TextileProducts _, TextileProducts _ ) -> + ( TextileExamples _, TextileExamples _ ) -> True ( TextileMaterials _, TextileMaterials _ ) -> @@ -196,6 +193,9 @@ same a b = ( TextileProcesses _, TextileProcesses _ ) -> True + ( TextileProducts _, TextileProducts _ ) -> + True + _ -> False @@ -206,9 +206,6 @@ setIdFromString idString dataset = Countries _ -> Countries (Just (Country.codeFromString idString)) - Impacts _ -> - Impacts (Definition.toTrigram idString |> Result.toMaybe) - FoodExamples _ -> FoodExamples (Uuid.fromString idString) @@ -218,108 +215,111 @@ setIdFromString idString dataset = FoodProcesses _ -> FoodProcesses (Just (FoodProcess.identifierFromString idString)) + Impacts _ -> + Impacts (Definition.toTrigram idString |> Result.toMaybe) + TextileExamples _ -> TextileExamples (Uuid.fromString idString) - TextileProducts _ -> - TextileProducts (Just (Product.Id idString)) - TextileMaterials _ -> TextileMaterials (Just (Material.Id idString)) TextileProcesses _ -> TextileProcesses (Just (Process.Uuid idString)) + TextileProducts _ -> + TextileProducts (Just (Product.Id idString)) + slug : Dataset -> String slug = strings >> .slug -strings : Dataset -> { slug : String, label : String } +strings : Dataset -> { label : String, slug : String } strings dataset = case dataset of Countries _ -> - { slug = "countries", label = "Pays" } - - Impacts _ -> - { slug = "impacts", label = "Impacts" } + { label = "Pays", slug = "countries" } FoodExamples _ -> - { slug = "food-examples", label = "Exemples" } + { label = "Exemples", slug = "food-examples" } FoodIngredients _ -> - { slug = "ingredients", label = "Ingrédients" } + { label = "Ingrédients", slug = "ingredients" } FoodProcesses _ -> - { slug = "food-processes", label = "Procédés" } + { label = "Procédés", slug = "food-processes" } - TextileExamples _ -> - { slug = "textile-examples", label = "Exemples" } + Impacts _ -> + { label = "Impacts", slug = "impacts" } - TextileProducts _ -> - { slug = "products", label = "Produits" } + TextileExamples _ -> + { label = "Exemples", slug = "textile-examples" } TextileMaterials _ -> - { slug = "materials", label = "Matières" } + { label = "Matières", slug = "materials" } TextileProcesses _ -> - { slug = "processes", label = "Procédés" } + { label = "Procédés", slug = "processes" } + + TextileProducts _ -> + { label = "Produits", slug = "products" } toRoutePath : Dataset -> List String toRoutePath dataset = case dataset of - Countries Nothing -> - [ slug dataset ] - Countries (Just code) -> [ slug dataset, Country.codeToString code ] - FoodExamples Nothing -> + Countries Nothing -> [ slug dataset ] FoodExamples (Just id) -> [ slug dataset, Uuid.toString id ] - FoodIngredients Nothing -> + FoodExamples Nothing -> [ slug dataset ] FoodIngredients (Just id) -> [ slug dataset, Ingredient.idToString id ] - FoodProcesses Nothing -> + FoodIngredients Nothing -> [ slug dataset ] FoodProcesses (Just id) -> [ slug dataset, FoodProcess.identifierToString id ] - Impacts Nothing -> + FoodProcesses Nothing -> [ slug dataset ] Impacts (Just trigram) -> [ slug dataset, Definition.toString trigram ] - TextileExamples Nothing -> + Impacts Nothing -> [ slug dataset ] TextileExamples (Just id) -> [ slug dataset, Uuid.toString id ] - TextileProducts Nothing -> + TextileExamples Nothing -> [ slug dataset ] - TextileProducts (Just id) -> - [ slug dataset, Product.idToString id ] + TextileMaterials (Just id) -> + [ slug dataset, Material.idToString id ] TextileMaterials Nothing -> [ slug dataset ] - TextileMaterials (Just id) -> - [ slug dataset, Material.idToString id ] + TextileProcesses (Just id) -> + [ slug dataset, Process.uuidToString id ] TextileProcesses Nothing -> [ slug dataset ] - TextileProcesses (Just id) -> - [ slug dataset, Process.uuidToString id ] + TextileProducts (Just id) -> + [ slug dataset, Product.idToString id ] + + TextileProducts Nothing -> + [ slug dataset ] diff --git a/src/Data/Example.elm b/src/Data/Example.elm index ca0d46c8a..adaaeafc7 100644 --- a/src/Data/Example.elm +++ b/src/Data/Example.elm @@ -15,9 +15,9 @@ import Url.Parser as Parser exposing (Parser) type alias Example query = - { id : Uuid + { category : String + , id : Uuid , name : String - , category : String , query : query } @@ -25,9 +25,9 @@ type alias Example query = decode : Decoder query -> Decoder (Example query) decode decodeQuery = Decode.map4 Example + (Decode.field "category" Decode.string) (Decode.field "id" Uuid.decoder) (Decode.field "name" Decode.string) - (Decode.field "category" Decode.string) (Decode.field "query" decodeQuery) diff --git a/src/Data/Food/Db.elm b/src/Data/Food/Db.elm index 858d5e3ac..115f32650 100644 --- a/src/Data/Food/Db.elm +++ b/src/Data/Food/Db.elm @@ -10,12 +10,13 @@ import Data.Food.Query as Query exposing (Query) import Data.Food.WellKnown as WellKnown exposing (WellKnown) import Data.Impact as Impact import Json.Decode as Decode +import Result.Extra as RE type alias Db = - { processes : List Process - , examples : List (Example Query) + { examples : List (Example Query) , ingredients : List Ingredient + , processes : List Process , wellKnown : WellKnown } @@ -27,8 +28,16 @@ buildFromJson exampleProductsJson foodProcessesJson ingredientsJson = |> Result.mapError Decode.errorToString |> Result.andThen (\processes -> - Result.map3 (Db processes) - (exampleProductsJson |> Example.decodeListFromJsonString Query.decode) - (ingredientsJson |> Decode.decodeString (Ingredient.decodeIngredients processes) |> Result.mapError Decode.errorToString) - (WellKnown.load processes) + Ok Db + |> RE.andMap + (exampleProductsJson + |> Example.decodeListFromJsonString Query.decode + ) + |> RE.andMap + (ingredientsJson + |> Decode.decodeString (Ingredient.decodeIngredients processes) + |> Result.mapError Decode.errorToString + ) + |> RE.andMap (Ok processes) + |> RE.andMap (WellKnown.load processes) ) diff --git a/src/Data/Food/EcosystemicServices.elm b/src/Data/Food/EcosystemicServices.elm index 2714d1024..2c56e7ebb 100644 --- a/src/Data/Food/EcosystemicServices.elm +++ b/src/Data/Food/EcosystemicServices.elm @@ -24,49 +24,49 @@ type alias Labels = type alias AbstractEcosystemicServices a = - { hedges : a - , plotSize : a - , cropDiversity : a - , permanentPasture : a + { cropDiversity : a + , hedges : a , livestockDensity : a + , permanentPasture : a + , plotSize : a } coefficients : Coefficients coefficients = - { hedges = Unit.ratio 3 - , plotSize = Unit.ratio 4 - , cropDiversity = Unit.ratio 1.5 - , permanentPasture = Unit.ratio 7 + { cropDiversity = Unit.ratio 1.5 + , hedges = Unit.ratio 3 , livestockDensity = Unit.ratio 3000 + , permanentPasture = Unit.ratio 7 + , plotSize = Unit.ratio 4 } decode : Decoder EcosystemicServices decode = Decode.succeed AbstractEcosystemicServices - |> Pipe.optional "hedges" Unit.decodeImpact (Unit.impact 0) - |> Pipe.optional "plotSize" Unit.decodeImpact (Unit.impact 0) |> Pipe.optional "cropDiversity" Unit.decodeImpact (Unit.impact 0) - |> Pipe.optional "permanentPasture" Unit.decodeImpact (Unit.impact 0) + |> Pipe.optional "hedges" Unit.decodeImpact (Unit.impact 0) |> Pipe.optional "livestockDensity" Unit.decodeImpact (Unit.impact 0) + |> Pipe.optional "permanentPasture" Unit.decodeImpact (Unit.impact 0) + |> Pipe.optional "plotSize" Unit.decodeImpact (Unit.impact 0) empty : EcosystemicServices empty = - { hedges = Unit.impact 0 - , plotSize = Unit.impact 0 - , cropDiversity = Unit.impact 0 - , permanentPasture = Unit.impact 0 + { cropDiversity = Unit.impact 0 + , hedges = Unit.impact 0 , livestockDensity = Unit.impact 0 + , permanentPasture = Unit.impact 0 + , plotSize = Unit.impact 0 } labels : Labels labels = - { hedges = "Haies" - , plotSize = "Taille de parcelles" - , cropDiversity = "Diversité culturale" - , permanentPasture = "Prairies permanentes" + { cropDiversity = "Diversité culturale" + , hedges = "Haies" , livestockDensity = "Chargement territorial" + , permanentPasture = "Prairies permanentes" + , plotSize = "Taille de parcelles" } diff --git a/src/Data/Food/Ingredient.elm b/src/Data/Food/Ingredient.elm index b215b1006..7e765aaf2 100644 --- a/src/Data/Food/Ingredient.elm +++ b/src/Data/Food/Ingredient.elm @@ -33,17 +33,17 @@ import Length type alias Ingredient = - { id : Id - , name : String - , categories : List IngredientCategory.Category + { categories : List IngredientCategory.Category , default : Process , defaultOrigin : Origin + , density : Density + , ecosystemicServices : EcosystemicServices + , id : Id , inediblePart : Split + , name : String , rawToCookedRatio : Unit.Ratio - , density : Density , transportCooling : TransportCooling , visible : Bool - , ecosystemicServices : EcosystemicServices } @@ -52,15 +52,15 @@ type Id type PlaneTransport - = PlaneNotApplicable - | ByPlane + = ByPlane | NoPlane + | PlaneNotApplicable type TransportCooling - = NoCooling - | AlwaysCool + = AlwaysCool | CoolOnceTransformed + | NoCooling byPlaneAllowed : PlaneTransport -> Ingredient -> Result String PlaneTransport @@ -100,15 +100,15 @@ encodeId (Id str) = encodePlaneTransport : PlaneTransport -> Maybe Encode.Value encodePlaneTransport planeTransport = case planeTransport of - PlaneNotApplicable -> - Nothing - ByPlane -> Just <| Encode.string "byPlane" NoPlane -> Just <| Encode.string "noPlane" + PlaneNotApplicable -> + Nothing + idFromString : String -> Id idFromString str = @@ -134,17 +134,17 @@ decodeIngredients processes = decodeIngredient : Dict String Process -> Decoder Ingredient decodeIngredient processes = Decode.succeed Ingredient - |> Pipe.required "id" decodeId - |> Pipe.required "name" Decode.string |> Pipe.required "categories" (Decode.list IngredientCategory.decode) |> Pipe.required "default" (linkProcess processes) |> Pipe.required "default_origin" Origin.decode + |> Pipe.required "density" (Decode.float |> Decode.map gramsPerCubicCentimeter) + |> Pipe.optional "ecosystemicServices" EcosystemicServices.decode EcosystemicServices.empty + |> 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 "density" (Decode.float |> Decode.map gramsPerCubicCentimeter) |> Pipe.required "transport_cooling" decodeTransportCooling |> Pipe.required "visible" Decode.bool - |> Pipe.optional "ecosystemicServices" EcosystemicServices.decode EcosystemicServices.empty decodeTransportCooling : Decoder TransportCooling @@ -182,18 +182,18 @@ getDefaultOriginTransport planeTransport origin = Transport.default Impact.empty in case origin of - Origin.France -> - default - Origin.EuropeAndMaghreb -> { default | road = Length.kilometers 2500 } + Origin.France -> + default + Origin.OutOfEuropeAndMaghreb -> { default | road = Length.kilometers 2500, sea = Length.kilometers 18000 } Origin.OutOfEuropeAndMaghrebByPlane -> if planeTransport == ByPlane then - { default | road = Length.kilometers 2500, air = Length.kilometers 18000 } + { default | air = Length.kilometers 18000, road = Length.kilometers 2500 } else { default | road = Length.kilometers 2500, sea = Length.kilometers 18000 } diff --git a/src/Data/Food/Ingredient/Category.elm b/src/Data/Food/Ingredient/Category.elm index 7cdf84bbc..10021ceea 100644 --- a/src/Data/Food/Ingredient/Category.elm +++ b/src/Data/Food/Ingredient/Category.elm @@ -10,18 +10,18 @@ import Json.Decode.Extra as DE type Category = AnimalProduct + | BleuBlancCoeur | Conventional | DairyProduct - | GrainRaw | GrainProcessed + | GrainRaw | Misc - | NutOilseedRaw | NutOilseedProcessed + | NutOilseedRaw + | Organic | SpiceCondimentOrAdditive | VegetableFresh | VegetableProcessed - | Organic - | BleuBlancCoeur fromString : String -> Result String Category @@ -76,26 +76,32 @@ toLabel category = AnimalProduct -> "Viandes, œufs, poissons, et dérivés" + BleuBlancCoeur -> + "Bleu-Blanc-Cœur" + Conventional -> "Conventionnel" DairyProduct -> "Lait et ingrédients laitiers" - GrainRaw -> - "Céréales brutes" - GrainProcessed -> "Céréales transformées" + GrainRaw -> + "Céréales brutes" + Misc -> "Divers" + NutOilseedProcessed -> + "Graisses végétales et oléoprotéagineux transformés" + NutOilseedRaw -> "Fruits à coque et oléoprotéagineux bruts" - NutOilseedProcessed -> - "Graisses végétales et oléoprotéagineux transformés" + Organic -> + "Bio" SpiceCondimentOrAdditive -> "Condiments, épices, additifs" @@ -106,12 +112,6 @@ toLabel category = VegetableProcessed -> "Fruits et légumes transformés" - Organic -> - "Bio" - - BleuBlancCoeur -> - "Bleu-Blanc-Cœur" - decode : Decoder Category decode = diff --git a/src/Data/Food/Origin.elm b/src/Data/Food/Origin.elm index 470e1dcbe..745b2dd84 100644 --- a/src/Data/Food/Origin.elm +++ b/src/Data/Food/Origin.elm @@ -9,8 +9,8 @@ import Json.Decode.Extra as DE type Origin - = France - | EuropeAndMaghreb + = EuropeAndMaghreb + | France | OutOfEuropeAndMaghreb | OutOfEuropeAndMaghrebByPlane @@ -24,12 +24,12 @@ decode = fromString : String -> Result String Origin fromString string = case string of - "France" -> - Ok France - "EuropeAndMaghreb" -> Ok EuropeAndMaghreb + "France" -> + Ok France + "OutOfEuropeAndMaghreb" -> Ok OutOfEuropeAndMaghreb @@ -43,12 +43,12 @@ fromString string = toLabel : Origin -> String toLabel origin = case origin of - France -> - "France" - EuropeAndMaghreb -> "Europe et Maghreb" + France -> + "France" + OutOfEuropeAndMaghreb -> "Hors Europe et Maghreb" diff --git a/src/Data/Food/Preparation.elm b/src/Data/Food/Preparation.elm index 750e151aa..19150f5b9 100644 --- a/src/Data/Food/Preparation.elm +++ b/src/Data/Food/Preparation.elm @@ -22,11 +22,11 @@ import Mass exposing (Mass) type alias Preparation = - { id : Id - , name : String + { applyRawToCookedRatio : Bool , elec : ( Energy, Split ) , heat : ( Energy, Split ) - , applyRawToCookedRatio : Bool + , id : Id + , name : String } @@ -37,47 +37,47 @@ type Id all : List Preparation all = -- see https://fabrique-numerique.gitbook.io/ecobalyse/alimentaire/etapes-du-cycles-de-vie/consommation#preparations-de-preparation - [ { id = Id "frying" - , name = "Friture" + [ { applyRawToCookedRatio = True , elec = ( Energy.kilowattHours 0.667, Split.full ) , heat = ( Energy.megajoules 0, Split.zero ) - , applyRawToCookedRatio = True + , id = Id "frying" + , name = "Friture" } - , { id = Id "pan-cooking" - , name = "Cuisson à la poêle" + , { applyRawToCookedRatio = True , elec = ( Energy.kilowattHours 0.44, Split.fourty ) , heat = ( Energy.megajoules 1.584, Split.complement Split.fourty ) - , applyRawToCookedRatio = True + , id = Id "pan-cooking" + , name = "Cuisson à la poêle" } - , { id = Id "pan-warming" - , name = "Réchauffage à la poêle" + , { applyRawToCookedRatio = False , elec = ( Energy.kilowattHours 0.08, Split.fourty ) , heat = ( Energy.megajoules 0.288, Split.complement Split.fourty ) - , applyRawToCookedRatio = False + , id = Id "pan-warming" + , name = "Réchauffage à la poêle" } - , { id = Id "oven" - , name = "Cuisson au four" + , { applyRawToCookedRatio = True , elec = ( Energy.kilowattHours 0.999, Split.full ) , heat = ( Energy.megajoules 0, Split.zero ) - , applyRawToCookedRatio = True + , id = Id "oven" + , name = "Cuisson au four" } - , { id = Id "microwave" - , name = "Cuisson au four micro-ondes" + , { applyRawToCookedRatio = True , elec = ( Energy.kilowattHours 0.128, Split.full ) , heat = ( Energy.megajoules 0, Split.zero ) - , applyRawToCookedRatio = True + , id = Id "microwave" + , name = "Cuisson au four micro-ondes" } - , { id = Id "refrigeration" - , name = "Réfrigération" + , { applyRawToCookedRatio = False , elec = ( Energy.kilowattHours 0.0777, Split.full ) , heat = ( Energy.megajoules 0, Split.zero ) - , applyRawToCookedRatio = False + , id = Id "refrigeration" + , name = "Réfrigération" } - , { id = Id "freezing" - , name = "Congélation" + , { applyRawToCookedRatio = False , elec = ( Energy.kilowattHours 0.294, Split.full ) , heat = ( Energy.megajoules 0, Split.zero ) - , applyRawToCookedRatio = False + , id = Id "freezing" + , name = "Congélation" } ] diff --git a/src/Data/Food/Process.elm b/src/Data/Food/Process.elm index 52272529e..a2bee15d6 100644 --- a/src/Data/Food/Process.elm +++ b/src/Data/Food/Process.elm @@ -30,16 +30,16 @@ A process is an entry from public/data/food/processes.json. It has impacts and various other data like categories, code, unit... -} type alias Process = - { name : ProcessName - , displayName : Maybe String - , impacts : Impacts - , unit : String - , identifier : Identifier - , category : Category - , systemDescription : String + { category : Category , comment : Maybe String + , displayName : Maybe String , id_ : String + , identifier : Identifier + , impacts : Impacts + , name : ProcessName , source : String + , systemDescription : String + , unit : String } @@ -183,31 +183,31 @@ encodeCategory = decodeProcess : Decoder Impact.Impacts -> Decoder Process decodeProcess impactsDecoder = Decode.succeed Process - |> Pipe.required "name" (Decode.map nameFromString Decode.string) - |> Pipe.optional "displayName" (Decode.maybe Decode.string) Nothing - |> Pipe.required "impacts" impactsDecoder - |> Pipe.required "unit" decodeStringUnit - |> Pipe.required "identifier" decodeIdentifier |> Pipe.required "category" decodeCategory - |> Pipe.required "system_description" Decode.string |> Pipe.optional "comment" (Decode.maybe Decode.string) Nothing + |> Pipe.optional "displayName" (Decode.maybe Decode.string) Nothing |> Pipe.required "id" Decode.string + |> Pipe.required "identifier" decodeIdentifier + |> Pipe.required "impacts" impactsDecoder + |> Pipe.required "name" (Decode.map nameFromString Decode.string) |> Pipe.required "source" Decode.string + |> Pipe.required "system_description" Decode.string + |> Pipe.required "unit" decodeStringUnit encode : Process -> Encode.Value encode process = Encode.object - [ ( "name", Encode.string (nameToString process.name) ) - , ( "displayName", EncodeExtra.maybe Encode.string process.displayName ) - , ( "impacts", Impact.encode process.impacts ) - , ( "unit", encodeStringUnit process.unit ) - , ( "identifier", encodeIdentifier process.identifier ) - , ( "category", encodeCategory process.category ) - , ( "system_description", Encode.string process.systemDescription ) + [ ( "category", encodeCategory process.category ) , ( "comment", EncodeExtra.maybe Encode.string process.comment ) + , ( "displayName", EncodeExtra.maybe Encode.string process.displayName ) , ( "id", Encode.string process.id_ ) + , ( "identifier", encodeIdentifier process.identifier ) + , ( "impacts", Impact.encode process.impacts ) + , ( "name", Encode.string (nameToString process.name) ) , ( "source", Encode.string process.source ) + , ( "system_description", Encode.string process.systemDescription ) + , ( "unit", encodeStringUnit process.unit ) ] @@ -306,12 +306,8 @@ encodeStringUnit unit = getDisplayName : Process -> String getDisplayName process = - case process.displayName of - Just displayName -> - displayName - - Nothing -> - nameToString process.name + process.displayName + |> Maybe.withDefault (nameToString process.name) listByCategory : Category -> List Process -> List Process diff --git a/src/Data/Food/Query.elm b/src/Data/Food/Query.elm index 4cfa04050..360643e40 100644 --- a/src/Data/Food/Query.elm +++ b/src/Data/Food/Query.elm @@ -38,9 +38,9 @@ import Url.Parser as Parser exposing (Parser) type alias IngredientQuery = - { id : Ingredient.Id + { country : Maybe Country.Code + , id : Ingredient.Id , mass : Mass - , country : Maybe Country.Code , planeTransport : Ingredient.PlaneTransport } @@ -52,11 +52,11 @@ type alias ProcessQuery = type alias Query = - { ingredients : List IngredientQuery - , transform : Maybe ProcessQuery + { distribution : Maybe Retail.Distribution + , ingredients : List IngredientQuery , packaging : List ProcessQuery - , distribution : Maybe Retail.Distribution , preparation : List Preparation.Id + , transform : Maybe ProcessQuery } @@ -102,11 +102,11 @@ buildApiQuery clientUrl query = decode : Decoder Query decode = Decode.succeed Query + |> Pipe.optional "distribution" (Decode.maybe Retail.decode) Nothing |> Pipe.required "ingredients" (Decode.list decodeIngredient) - |> Pipe.optional "transform" (Decode.maybe decodeProcess) Nothing |> Pipe.optional "packaging" (Decode.list decodeProcess) [] - |> Pipe.optional "distribution" (Decode.maybe Retail.decode) Nothing |> Pipe.optional "preparation" (Decode.list Preparation.decodeId) [] + |> Pipe.optional "transform" (Decode.maybe decodeProcess) Nothing decodePlaneTransport : Decoder Ingredient.PlaneTransport @@ -153,9 +153,9 @@ decodeProcess = decodeIngredient : Decoder IngredientQuery decodeIngredient = Decode.succeed IngredientQuery + |> Pipe.optional "country" (Decode.maybe Country.decodeCode) Nothing |> Pipe.required "id" Ingredient.decodeId |> Pipe.required "mass" decodeMassInGrams - |> Pipe.optional "country" (Decode.maybe Country.decodeCode) Nothing |> Pipe.optional "byPlane" decodePlaneTransport Ingredient.PlaneNotApplicable @@ -179,11 +179,11 @@ deleteIngredient id query = empty : Query empty = - { ingredients = [] - , transform = Nothing + { distribution = Nothing + , ingredients = [] , packaging = [] - , distribution = Nothing , preparation = [] + , transform = Nothing } diff --git a/src/Data/Food/Recipe.elm b/src/Data/Food/Recipe.elm index 3c8c0e5a7..ccbcf8e03 100644 --- a/src/Data/Food/Recipe.elm +++ b/src/Data/Food/Recipe.elm @@ -56,33 +56,33 @@ france = type alias Packaging = - { process : Process.Process - , mass : Mass + { mass : Mass + , process : Process.Process } type alias RecipeIngredient = - { ingredient : Ingredient + { country : Maybe Country + , ingredient : Ingredient , mass : Mass - , country : Maybe Country , planeTransport : Ingredient.PlaneTransport } type alias Recipe = - { ingredients : List RecipeIngredient - , transform : Maybe Transform + { distribution : Maybe Retail.Distribution + , ingredients : List RecipeIngredient , packaging : List Packaging - , distribution : Maybe Retail.Distribution , preparation : List Preparation + , transform : Maybe Transform } type alias Results = - { total : Impacts + { distribution : { total : Impacts, transports : Transport } + , packaging : Impacts , perKg : Impacts - , scoring : Scoring - , totalMass : Mass + , preparation : Impacts , preparedMass : Mass , recipe : { total : Impacts @@ -96,19 +96,16 @@ type alias Results = , transports : Transport , transformedMass : Mass } - , packaging : Impacts - , distribution : - { total : Impacts - , transports : Transport - } - , preparation : Impacts + , scoring : Scoring + , total : Impacts + , totalMass : Mass , transports : Transport } type alias Transform = - { process : Process.Process - , mass : Mass + { mass : Mass + , process : Process.Process } @@ -248,55 +245,46 @@ compute db = (Impact.getTotalComplementsImpacts totalComplementsImpactPerKg) in ( recipe - , { total = totalImpacts + , { distribution = { total = distributionImpacts, transports = distributionTransport } + , packaging = packagingImpacts , perKg = impactsPerKg - , scoring = scoring - , totalMass = getMassAtPackaging recipe + , preparation = preparationImpacts , preparedMass = preparedMass , recipe = - { total = addIngredientsComplements recipeImpacts - , initialMass = recipe.ingredients |> List.map .mass |> Quantity.sum - , edibleMass = removeIngredientsInedibleMass recipe.ingredients |> List.map .mass |> Quantity.sum - , ingredientsTotal = addIngredientsComplements ingredientsTotalImpacts + { edibleMass = removeIngredientsInedibleMass recipe.ingredients |> List.map .mass |> Quantity.sum , ingredients = ingredientsImpacts + , ingredientsTotal = addIngredientsComplements ingredientsTotalImpacts + , initialMass = recipe.ingredients |> List.map .mass |> Quantity.sum + , total = addIngredientsComplements recipeImpacts , totalComplementsImpact = totalComplementsImpact , totalComplementsImpactPerKg = totalComplementsImpactPerKg , transform = transformImpacts - , transports = ingredientsTransport , transformedMass = transformedIngredientsMass + , transports = ingredientsTransport } - , packaging = packagingImpacts - , distribution = - { total = distributionImpacts - , transports = distributionTransport - } - , preparation = preparationImpacts - , transports = - Transport.sum - [ ingredientsTransport - , distributionTransport - ] + , scoring = scoring + , total = totalImpacts + , totalMass = getMassAtPackaging recipe + , transports = Transport.sum [ ingredientsTransport, distributionTransport ] } ) ) computeIngredientComplementsImpacts : EcosystemicServices -> Mass -> Impact.ComplementsImpacts -computeIngredientComplementsImpacts { hedges, plotSize, cropDiversity, permanentPasture, livestockDensity } ingredientMass = +computeIngredientComplementsImpacts { cropDiversity, hedges, livestockDensity, permanentPasture, plotSize } ingredientMass = let apply coeff = Quantity.multiplyBy (Mass.inKilograms ingredientMass) >> Quantity.multiplyBy (Unit.ratioToFloat coeff) in - { hedges = apply EcosystemicServices.coefficients.hedges hedges - , plotSize = apply EcosystemicServices.coefficients.plotSize plotSize - , cropDiversity = apply EcosystemicServices.coefficients.cropDiversity cropDiversity - , permanentPasture = apply EcosystemicServices.coefficients.permanentPasture permanentPasture + { cropDiversity = apply EcosystemicServices.coefficients.cropDiversity cropDiversity + , hedges = apply EcosystemicServices.coefficients.hedges hedges , livestockDensity = apply EcosystemicServices.coefficients.livestockDensity livestockDensity - - -- Note: these complements don't apply to ingredients , microfibers = Unit.impact 0 , outOfEuropeEOL = Unit.impact 0 + , permanentPasture = apply EcosystemicServices.coefficients.permanentPasture permanentPasture + , plotSize = apply EcosystemicServices.coefficients.plotSize plotSize } @@ -307,7 +295,7 @@ computeImpact mass _ = >> Unit.impact -computeProcessImpacts : { a | process : Process, mass : Mass } -> Impacts +computeProcessImpacts : { a | mass : Mass, process : Process } -> Impacts computeProcessImpacts item = item.process.impacts |> Impact.mapImpacts (computeImpact item.mass) @@ -333,7 +321,7 @@ computeIngredientsTotalComplements = computeIngredientTransport : Db -> RecipeIngredient -> Transport -computeIngredientTransport db { ingredient, country, mass, planeTransport } = +computeIngredientTransport db { country, ingredient, mass, planeTransport } = let emptyImpacts = Impact.empty @@ -466,11 +454,11 @@ encodeScoring scoring = fromQuery : Db -> Query -> Result String Recipe fromQuery db query = Ok Recipe + |> RE.andMap (Ok query.distribution) |> RE.andMap (ingredientListFromQuery db query) - |> RE.andMap (transformFromQuery db.food query) |> RE.andMap (packagingListFromQuery db.food query) - |> RE.andMap (Ok query.distribution) |> RE.andMap (preparationListFromQuery query) + |> RE.andMap (transformFromQuery db.food query) getMassAtPackaging : Recipe -> Mass @@ -579,14 +567,12 @@ ingredientListFromQuery db = ingredientFromQuery : Db -> BuilderQuery.IngredientQuery -> Result String RecipeIngredient -ingredientFromQuery db { id, mass, country, planeTransport } = +ingredientFromQuery db { country, id, mass, planeTransport } = let ingredientResult = Ingredient.findByID id db.food.ingredients in Ok RecipeIngredient - |> RE.andMap ingredientResult - |> RE.andMap (Ok mass) |> RE.andMap (case Maybe.map (\c -> Country.findByCode c db.countries) country of Just (Ok country_) -> @@ -598,6 +584,8 @@ ingredientFromQuery db { id, mass, country, planeTransport } = Nothing -> Ok Nothing ) + |> RE.andMap ingredientResult + |> RE.andMap (Ok mass) |> RE.andMap (ingredientResult |> Result.andThen (Ingredient.byPlaneAllowed planeTransport) @@ -606,9 +594,9 @@ ingredientFromQuery db { id, mass, country, planeTransport } = ingredientQueryFromIngredient : Ingredient -> BuilderQuery.IngredientQuery ingredientQueryFromIngredient ingredient = - { id = ingredient.id + { country = Nothing + , id = ingredient.id , mass = Mass.grams 100 - , country = Nothing , planeTransport = Ingredient.byPlaneByDefault ingredient } @@ -624,9 +612,9 @@ packagingListFromQuery db query = packagingFromQuery : Food.Db -> BuilderQuery.ProcessQuery -> Result String Packaging packagingFromQuery { processes } { code, mass } = - Result.map2 Packaging - (Process.findByIdentifier code processes) - (Ok mass) + processes + |> Process.findByIdentifier code + |> Result.map (Packaging mass) processQueryFromProcess : Process -> BuilderQuery.ProcessQuery @@ -653,18 +641,18 @@ toStepsImpacts trigram results = Impact.getImpact trigram >> Just in - { materials = getImpact results.recipe.ingredientsTotal - , transform = getImpact results.recipe.transform + { distribution = getImpact results.distribution.total + , endOfLife = Nothing + , materials = getImpact results.recipe.ingredientsTotal , packaging = getImpact results.packaging + , transform = getImpact results.recipe.transform , transports = getImpact results.transports.impacts - , distribution = getImpact results.distribution.total , usage = getImpact results.preparation - , endOfLife = Nothing } toString : Recipe -> String -toString { ingredients, transform, packaging } = +toString { ingredients, packaging, transform } = let formatMass = Mass.inGrams >> round >> String.fromInt @@ -678,7 +666,7 @@ toString { ingredients, transform, packaging } = |> SE.nonEmpty , transform |> Maybe.map - (\{ process, mass } -> + (\{ mass, process } -> Process.getDisplayName process ++ "(" ++ formatMass mass ++ ")" ) , packaging @@ -699,9 +687,8 @@ transformFromQuery { processes } query = query.transform |> Maybe.map (\transform -> - Result.map2 Transform - (Process.findByIdentifier transform.code processes) - (Ok transform.mass) - |> Result.map Just + processes + |> Process.findByIdentifier transform.code + |> Result.map (Transform transform.mass >> Just) ) |> Maybe.withDefault (Ok Nothing) diff --git a/src/Data/Food/Retail.elm b/src/Data/Food/Retail.elm index aebff9c3a..dac3d7a72 100644 --- a/src/Data/Food/Retail.elm +++ b/src/Data/Food/Retail.elm @@ -46,10 +46,10 @@ type Type type alias Needs = --- what it needs to store a product at the retail store - { energy : Quantity Float (Rate Joules CubicMeters) - , cooling : Quantity Float (Rate Joules CubicMeters) - , water : Float + { cooling : Quantity Float (Rate Joules CubicMeters) + , energy : Quantity Float (Rate Joules CubicMeters) , transport : Length + , water : Float } @@ -60,30 +60,30 @@ type alias Needs = ambient : Distribution ambient = Distribution Ambient - { energy = rate (kilowattHours 123.08) (cubicMeters 1) - , cooling = rate (kilowattHours 0) (cubicMeters 1) - , water = ratio (liters 561.5) (cubicMeters 1) + { cooling = rate (kilowattHours 0) (cubicMeters 1) + , energy = rate (kilowattHours 123.08) (cubicMeters 1) , transport = Length.kilometers 600 + , water = ratio (liters 561.5) (cubicMeters 1) } fresh : Distribution fresh = Distribution Fresh - { energy = rate (kilowattHours 46.15) (cubicMeters 1) - , cooling = rate (kilowattHours 219.23) (cubicMeters 1) - , water = ratio (liters 210.6) (cubicMeters 1) + { cooling = rate (kilowattHours 219.23) (cubicMeters 1) + , energy = rate (kilowattHours 46.15) (cubicMeters 1) , transport = Length.kilometers 600 + , water = ratio (liters 210.6) (cubicMeters 1) } frozen : Distribution frozen = Distribution Frozen - { energy = rate (kilowattHours 61.54) (cubicMeters 1) - , cooling = rate (kilowattHours 415.38) (cubicMeters 1) - , water = ratio (liters 280.8) (cubicMeters 1) + { cooling = rate (kilowattHours 415.38) (cubicMeters 1) + , energy = rate (kilowattHours 61.54) (cubicMeters 1) , transport = Length.kilometers 600 + , water = ratio (liters 280.8) (cubicMeters 1) } diff --git a/src/Data/Food/WellKnown.elm b/src/Data/Food/WellKnown.elm index fc2d345b5..64cec58b0 100644 --- a/src/Data/Food/WellKnown.elm +++ b/src/Data/Food/WellKnown.elm @@ -8,14 +8,14 @@ import Result.Extra as RE type alias WellKnown = - { lorryTransport : Process + { boatCoolingTransport : Process , boatTransport : Process - , planeTransport : Process + , domesticGasHeat : Process , lorryCoolingTransport : Process - , boatCoolingTransport : Process - , water : Process + , lorryTransport : Process , lowVoltageElectricity : Process - , domesticGasHeat : Process + , planeTransport : Process + , water : Process } @@ -26,11 +26,11 @@ load processes = RE.andMap (Process.findById processes id_) in Ok WellKnown - |> resolve "lorry" + |> resolve "boat-cooling" |> resolve "boat" - |> resolve "plane" + |> resolve "domestic-gas-heat" |> resolve "lorry-cooling" - |> resolve "boat-cooling" - |> resolve "tap-water" + |> resolve "lorry" |> resolve "low-voltage-electricity" - |> resolve "domestic-gas-heat" + |> resolve "plane" + |> resolve "tap-water" diff --git a/src/Data/Gitbook.elm b/src/Data/Gitbook.elm index 03a521d52..496470af3 100644 --- a/src/Data/Gitbook.elm +++ b/src/Data/Gitbook.elm @@ -1,21 +1,11 @@ module Data.Gitbook exposing - ( Page - , Path(..) - , handleMarkdownGitbookLink + ( Path(..) , publicUrlFromPath ) import Data.Env as Env -type alias Page = - { title : String - , description : Maybe String - , markdown : String - , path : Path - } - - type Path = FoodComplements -- Bonus et compléments hors-ACV | FoodDistribution -- Distribution @@ -40,11 +30,11 @@ type Path | TextileFabric -- Tissage/Tricotage textile | TextileFabricWaste -- Taux de perte en tissage/tricotage textile | TextileHeat -- Chaleur textile - | TextileMaterial -- Matière textile | TextileMaking -- Confection textile | TextileMakingComplexity -- Complexité de la confection textile | TextileMakingDeadStock -- Deadstock lors de la confection textile | TextileMakingWaste -- Taux de perte en confection textile + | TextileMaterial -- Matière textile | TextileSpinning -- Filature textile | TextileTransport -- Transport textile | TextileUse -- Utilisation textile @@ -104,12 +94,12 @@ pathToString path = TextileEnnobling -> "textile/etapes-du-cycle-de-vie/ennoblissement" - TextileEnnoblingToxicity -> - "textile/etapes-du-cycle-de-vie/ennoblissement/inventaires-enrichis" - TextileEnnoblingCountriesAquaticPollution -> "textile/etapes-du-cycle-de-vie/ennoblissement/inventaires-enrichis#pays-less-than-greater-than-taux-de-pollution-aquatique" + TextileEnnoblingToxicity -> + "textile/etapes-du-cycle-de-vie/ennoblissement/inventaires-enrichis" + TextileExamples -> "textile/exemples" @@ -122,9 +112,6 @@ pathToString path = TextileHeat -> "textile/parametres-transverses/chaleur" - TextileMaterial -> - "textile/etapes-du-cycle-de-vie/etape-1-matieres" - TextileMaking -> "textile/etapes-du-cycle-de-vie/confection" @@ -137,6 +124,9 @@ pathToString path = TextileMakingWaste -> "textile/parametres-transverses/pertes-et-rebus" + TextileMaterial -> + "textile/etapes-du-cycle-de-vie/etape-1-matieres" + TextileSpinning -> "textile/etapes-du-cycle-de-vie/etape-2-fabrication-du-fil-new" @@ -147,11 +137,6 @@ pathToString path = "textile/etapes-du-cycle-de-vie/etape-6-utilisation" -pathPrefixes : List String -pathPrefixes = - [ "faq", "glossaire", "methodologie" ] - - publicUrlFromPath : Path -> String publicUrlFromPath = pathToString >> publicUrlFromString @@ -160,37 +145,3 @@ publicUrlFromPath = publicUrlFromString : String -> String publicUrlFromString path = Env.gitbookUrl ++ "/" ++ path - - -handleMarkdownGitbookLink : Maybe Path -> String -> String -handleMarkdownGitbookLink maybePath link = - if List.any (\x -> String.startsWith x link) pathPrefixes then - publicUrlFromString link - - else if String.endsWith ".md" link then - case maybePath of - Just path -> - -- check for current folder, eg. "filature.md", "../faq.md", "methodologie/transport.md" - (extractLinkFolder path ++ [ String.replace ".md" "" link ]) - |> String.join "/" - |> publicUrlFromString - - Nothing -> - publicUrlFromString link - - else - link - - -extractLinkFolder : Path -> List String -extractLinkFolder path = - case String.split "/" (pathToString path) of - folder :: _ -> - if folder == ".." then - [] - - else - [ folder ] - - _ -> - [] diff --git a/src/Data/Impact.elm b/src/Data/Impact.elm index 1522ecd6a..b78689db3 100644 --- a/src/Data/Impact.elm +++ b/src/Data/Impact.elm @@ -56,30 +56,26 @@ import Url.Parser as Parser exposing (Parser) type alias ComplementsImpacts = -- Note: these are always expressed in ecoscore (ecs) Pts { -- Ecosystemic services impacts - hedges : Unit.Impact - , plotSize : Unit.Impact - , cropDiversity : Unit.Impact - , permanentPasture : Unit.Impact + cropDiversity : Unit.Impact + , hedges : Unit.Impact , livestockDensity : Unit.Impact - - -- Other impacts , microfibers : Unit.Impact , outOfEuropeEOL : Unit.Impact + , permanentPasture : Unit.Impact + , plotSize : Unit.Impact } addComplementsImpacts : ComplementsImpacts -> ComplementsImpacts -> ComplementsImpacts addComplementsImpacts a b = { -- Ecosystemic services impacts - hedges = Quantity.plus a.hedges b.hedges - , plotSize = Quantity.plus a.plotSize b.plotSize - , cropDiversity = Quantity.plus a.cropDiversity b.cropDiversity - , permanentPasture = Quantity.plus a.permanentPasture b.permanentPasture + cropDiversity = Quantity.plus a.cropDiversity b.cropDiversity + , hedges = Quantity.plus a.hedges b.hedges , livestockDensity = Quantity.plus a.livestockDensity b.livestockDensity - - -- Other impacts , microfibers = Quantity.plus a.microfibers b.microfibers , outOfEuropeEOL = Quantity.plus a.outOfEuropeEOL b.outOfEuropeEOL + , permanentPasture = Quantity.plus a.permanentPasture b.permanentPasture + , plotSize = Quantity.plus a.plotSize b.plotSize } @@ -106,41 +102,38 @@ encodeComplementsImpacts complementsImpact = negateComplementsImpacts complementsImpact in Encode.object - -- Ecosystemic services - [ ( "hedges", Unit.encodeImpact negated.hedges ) - , ( "plotSize", Unit.encodeImpact negated.plotSize ) - , ( "cropDiversity", Unit.encodeImpact negated.cropDiversity ) - , ( "permanentPasture", Unit.encodeImpact negated.permanentPasture ) + [ ( "cropDiversity", Unit.encodeImpact negated.cropDiversity ) + , ( "hedges", Unit.encodeImpact negated.hedges ) , ( "livestockDensity", Unit.encodeImpact negated.livestockDensity ) - - -- Textile complements , ( "microfibers", Unit.encodeImpact negated.microfibers ) , ( "outOfEuropeEOL", Unit.encodeImpact negated.outOfEuropeEOL ) + , ( "permanentPasture", Unit.encodeImpact negated.permanentPasture ) + , ( "plotSize", Unit.encodeImpact negated.plotSize ) ] getTotalComplementsImpacts : ComplementsImpacts -> Unit.Impact getTotalComplementsImpacts complementsImpacts = Quantity.sum - [ complementsImpacts.hedges - , complementsImpacts.plotSize - , complementsImpacts.cropDiversity - , complementsImpacts.permanentPasture + [ complementsImpacts.cropDiversity + , complementsImpacts.hedges , complementsImpacts.livestockDensity , complementsImpacts.microfibers , complementsImpacts.outOfEuropeEOL + , complementsImpacts.permanentPasture + , complementsImpacts.plotSize ] mapComplementsImpacts : (Unit.Impact -> Unit.Impact) -> ComplementsImpacts -> ComplementsImpacts mapComplementsImpacts fn ci = - { hedges = fn ci.hedges - , plotSize = fn ci.plotSize - , cropDiversity = fn ci.cropDiversity - , permanentPasture = fn ci.permanentPasture + { cropDiversity = fn ci.cropDiversity + , hedges = fn ci.hedges , livestockDensity = fn ci.livestockDensity , microfibers = fn ci.microfibers , outOfEuropeEOL = fn ci.outOfEuropeEOL + , permanentPasture = fn ci.permanentPasture + , plotSize = fn ci.plotSize } @@ -151,13 +144,13 @@ negateComplementsImpacts = noComplementsImpacts : ComplementsImpacts noComplementsImpacts = - { hedges = Unit.impact 0 - , plotSize = Unit.impact 0 - , cropDiversity = Unit.impact 0 - , permanentPasture = Unit.impact 0 + { cropDiversity = Unit.impact 0 + , hedges = Unit.impact 0 , livestockDensity = Unit.impact 0 , microfibers = Unit.impact 0 , outOfEuropeEOL = Unit.impact 0 + , permanentPasture = Unit.impact 0 + , plotSize = Unit.impact 0 } @@ -179,31 +172,31 @@ impactsWithComplements complementsImpacts impacts = sumEcosystemicImpacts : ComplementsImpacts -> Unit.Impact sumEcosystemicImpacts c = Quantity.sum - [ c.hedges - , c.plotSize - , c.cropDiversity - , c.permanentPasture + [ c.cropDiversity + , c.hedges , c.livestockDensity + , c.permanentPasture + , c.plotSize ] -complementsImpactAsChartEntries : ComplementsImpacts -> List { name : String, value : Float, color : String } +complementsImpactAsChartEntries : ComplementsImpacts -> List { color : String, name : String, value : Float } complementsImpactAsChartEntries c = -- Notes: -- - We want those complements/bonuses to appear as negative values on the chart -- - We want to sum ecosystemic service components impacts to only have a single entry in the charts - [ { name = "Services écosystémiques", value = -(Unit.impactToFloat (sumEcosystemicImpacts c)), color = "#606060" } - , { name = "Complément microfibres", value = -(Unit.impactToFloat c.microfibers), color = "#c0c0c0" } - , { name = "Complément export hors-Europe", value = -(Unit.impactToFloat c.outOfEuropeEOL), color = "#e0e0e0" } + [ { color = "#606060", name = "Services écosystémiques", value = -(Unit.impactToFloat (sumEcosystemicImpacts c)) } + , { color = "#c0c0c0", name = "Complément microfibres", value = -(Unit.impactToFloat c.microfibers) } + , { color = "#e0e0e0", name = "Complément export hors-Europe", value = -(Unit.impactToFloat c.outOfEuropeEOL) } ] -totalComplementsImpactAsChartEntry : ComplementsImpacts -> { name : String, value : Float, color : String } +totalComplementsImpactAsChartEntry : ComplementsImpacts -> { color : String, name : String, value : Float } totalComplementsImpactAsChartEntry complementsImpacts = -- We want bonuses to appear as negative values on the chart, maluses as positive ones - { name = "Compléments" + { color = "#808080" + , name = "Compléments" , value = -(Unit.impactToFloat (getTotalComplementsImpacts complementsImpacts)) - , color = "#808080" } @@ -212,13 +205,13 @@ totalComplementsImpactAsChartEntry complementsImpacts = type alias Steps a = - { materials : a - , transform : a + { distribution : a + , endOfLife : a + , materials : a , packaging : a + , transform : a , transports : a - , distribution : a , usage : a - , endOfLife : a } @@ -228,25 +221,25 @@ type alias StepsImpacts = mapSteps : (a -> a) -> Steps a -> Steps a mapSteps fn steps = - { materials = fn steps.materials - , transform = fn steps.transform + { distribution = fn steps.distribution + , endOfLife = fn steps.endOfLife + , materials = fn steps.materials , packaging = fn steps.packaging + , transform = fn steps.transform , transports = fn steps.transports - , distribution = fn steps.distribution , usage = fn steps.usage - , endOfLife = fn steps.endOfLife } noStepsImpacts : StepsImpacts noStepsImpacts = - { materials = Nothing - , transform = Nothing + { distribution = Nothing + , endOfLife = Nothing + , materials = Nothing , packaging = Nothing + , transform = Nothing , transports = Nothing - , distribution = Nothing , usage = Nothing - , endOfLife = Nothing } @@ -261,17 +254,17 @@ type alias StepsColors = stepsColors : StepsColors stepsColors = - { materials = Color.purple - , transform = Color.pink + { distribution = Color.red + , endOfLife = Color.turquoise + , materials = Color.purple , packaging = Color.blue + , transform = Color.pink , transports = Color.green - , distribution = Color.red , usage = Color.yellow - , endOfLife = Color.turquoise } -stepsImpactsAsChartEntries : StepsImpacts -> List { name : String, value : Float, color : String } +stepsImpactsAsChartEntries : StepsImpacts -> List { color : String, name : String, value : Float } stepsImpactsAsChartEntries stepsImpacts = [ ( "Matières premières", stepsImpacts.materials, stepsColors.materials ) , ( "Transformation", stepsImpacts.transform, stepsColors.transform ) @@ -283,8 +276,8 @@ stepsImpactsAsChartEntries stepsImpacts = ] |> List.map (\( label, maybeValue, color ) -> - { name = label - , color = color + { color = color + , name = label , value = -- All categories MUST be filled in order to allow comparing Food and Textile simulations -- So, when we don't have a value for a given step, we fallback to zero @@ -301,10 +294,10 @@ stepsImpactsAsChartEntries stepsImpacts = type alias ProtectionAreas = -- Protection Areas is basically scientific slang for subscores - { climate : Unit.Impact -- Climat - , biodiversity : Unit.Impact -- Biodiversité - , resources : Unit.Impact -- Ressources + { biodiversity : Unit.Impact -- Biodiversité + , climate : Unit.Impact -- Climat , health : Unit.Impact -- Santé environnementale + , resources : Unit.Impact -- Ressources } @@ -317,11 +310,7 @@ toProtectionAreas definitions (Impacts impactsPerKgWithoutComplements) = |> Impacts |> computeAggregatedScore definitions .ecoscoreData in - { climate = - pick - [ Definition.Cch -- Climate change - ] - , biodiversity = + { biodiversity = pick [ Definition.Acd -- Acidification , Definition.Tre -- Terrestrial eutrophication @@ -330,6 +319,10 @@ toProtectionAreas definitions (Impacts impactsPerKgWithoutComplements) = , Definition.EtfC -- Ecotoxicity: freshwater , Definition.Ldu -- Land use ] + , climate = + pick + [ Definition.Cch -- Climate change + ] , health = pick [ Definition.Ozd -- Ozone depletion @@ -471,12 +464,12 @@ getAggregatedScoreData definitions getter (Impacts impacts) = in case getter def of Just { normalization, weighting, color } -> - { name = def.label + { color = color ++ "bb" -- pastelization through slight transparency + , name = def.label , value = impact |> Unit.impactAggregateScore normalization weighting |> Unit.impactToFloat - , color = color ++ "bb" -- pastelization through slight transparency } :: acc @@ -487,7 +480,7 @@ getAggregatedScoreData definitions getter (Impacts impacts) = impacts -encodeAggregatedScoreChartEntry : { name : String, value : Float, color : String } -> Encode.Value +encodeAggregatedScoreChartEntry : { color : String, name : String, value : Float } -> Encode.Value encodeAggregatedScoreChartEntry entry = -- This is to be easily used with Highcharts.js in a Web Component Encode.object diff --git a/src/Data/Matomo.elm b/src/Data/Matomo.elm index 743962fb5..55945da06 100644 --- a/src/Data/Matomo.elm +++ b/src/Data/Matomo.elm @@ -9,8 +9,8 @@ import Time exposing (Posix) type alias Stat = - { label : String - , hits : Int + { hits : Int + , label : String , time : Posix } @@ -29,18 +29,20 @@ decodeStats key = (Dict.toList >> List.map (\( label, hits ) -> - Iso8601.toTime label - |> Result.map (Stat label hits) + Ok Stat + |> RE.andMap (Ok hits) + |> RE.andMap (Ok label) + |> RE.andMap (Iso8601.toTime label) |> Result.mapError (always ("Format de date invalide: " ++ label)) ) >> RE.combine >> (\res -> case res of - Ok list -> - Decode.succeed list - Err err -> Decode.fail err + + Ok list -> + Decode.succeed list ) ) @@ -49,7 +51,7 @@ encodeStats : List Stat -> String encodeStats stats = stats |> Encode.list - (\{ time, hits } -> + (\{ hits, time } -> -- The format for Highcharts' line chart is [[timestamp, value], …] Encode.list Encode.int [ Time.posixToMillis time, hits ] ) diff --git a/src/Data/Scoring.elm b/src/Data/Scoring.elm index 7bc6e573a..65fd5b866 100644 --- a/src/Data/Scoring.elm +++ b/src/Data/Scoring.elm @@ -13,9 +13,9 @@ import Quantity type alias Scoring = { all : Unit.Impact , allWithoutComplements : Unit.Impact - , complements : Unit.Impact - , climate : Unit.Impact , biodiversity : Unit.Impact + , climate : Unit.Impact + , complements : Unit.Impact , health : Unit.Impact , resources : Unit.Impact } @@ -34,9 +34,9 @@ compute definitions totalComplementsImpactPerKg perKgWithoutComplements = in { all = Quantity.difference ecsPerKgWithoutComplements totalComplementsImpactPerKg , allWithoutComplements = ecsPerKgWithoutComplements - , complements = totalComplementsImpactPerKg - , climate = subScores.climate , biodiversity = subScores.biodiversity + , climate = subScores.climate + , complements = totalComplementsImpactPerKg , health = subScores.health , resources = subScores.resources } @@ -46,9 +46,9 @@ empty : Scoring empty = { all = Unit.impact 0 , allWithoutComplements = Unit.impact 0 - , complements = Unit.impact 0 - , climate = Unit.impact 0 , biodiversity = Unit.impact 0 + , climate = Unit.impact 0 + , complements = Unit.impact 0 , health = Unit.impact 0 , resources = Unit.impact 0 } diff --git a/src/Data/Session.elm b/src/Data/Session.elm index d9dc01f0d..2321245da 100644 --- a/src/Data/Session.elm +++ b/src/Data/Session.elm @@ -39,19 +39,16 @@ import Static.Json as StaticJson exposing (RawJsonProcesses) type alias Session = - { db : Db - , navKey : Nav.Key - , clientUrl : String - , enableFoodSection : Bool - , store : Store + { clientUrl : String , currentVersion : Version + , db : Db + , enableFoodSection : Bool , matomo : { host : String, siteId : String } + , navKey : Nav.Key , notifications : List Notification - , queries : - { food : FoodQuery.Query - , textile : TextileQuery.Query - } + , queries : { food : FoodQuery.Query, textile : TextileQuery.Query } , releases : WebData (List Github.Release) + , store : Store } @@ -188,31 +185,31 @@ selectNoBookmarks = type alias Store = - { comparedSimulations : Set String + { auth : Auth , bookmarks : List Bookmark - , auth : Auth + , comparedSimulations : Set String } type Auth - = NotAuthenticated - | Authenticated User + = Authenticated User + | NotAuthenticated defaultStore : Store defaultStore = - { comparedSimulations = Set.empty + { auth = NotAuthenticated , bookmarks = [] - , auth = NotAuthenticated + , comparedSimulations = Set.empty } decodeStore : Decoder Store decodeStore = Decode.succeed Store - |> JDP.optional "comparedSimulations" (Decode.map Set.fromList (Decode.list Decode.string)) Set.empty - |> JDP.optional "bookmarks" (Decode.list Bookmark.decode) [] |> JDP.optional "auth" decodeAuth NotAuthenticated + |> JDP.optional "bookmarks" (Decode.list Bookmark.decode) [] + |> JDP.optional "comparedSimulations" (Decode.map Set.fromList (Decode.list Decode.string)) Set.empty decodeAuth : Decoder Auth @@ -233,12 +230,12 @@ encodeStore store = encodeAuth : Auth -> Encode.Value encodeAuth auth = case auth of - NotAuthenticated -> - Encode.null - Authenticated user -> Encode.object [ ( "user", User.encode user ) ] + NotAuthenticated -> + Encode.null + getUser : Session -> Maybe User getUser { store } = @@ -285,24 +282,24 @@ updateStore update session = authenticated : User -> RawJsonProcesses -> Session -> Session authenticated user rawDetailedProcessesJson ({ store } as session) = case StaticDb.db rawDetailedProcessesJson of - Ok db -> - { session | db = db, store = { store | auth = Authenticated user } } - Err err -> session |> notifyError "Impossible de recharger la db avec les nouveaux procédés" err + Ok db -> + { session | db = db, store = { store | auth = Authenticated user } } + logout : Session -> Session logout ({ store } as session) = case StaticDb.db StaticJson.rawJsonProcesses of - Ok db -> - { session | store = { store | auth = NotAuthenticated }, db = db } - Err err -> { session | store = { store | auth = NotAuthenticated } } |> notifyError "Impossible de recharger la db avec les procédés par défaut" err + Ok db -> + { session | db = db, store = { store | auth = NotAuthenticated } } + isAuthenticated : Session -> Bool isAuthenticated { store } = diff --git a/src/Data/Split.elm b/src/Data/Split.elm index c1e8865aa..e3285a276 100644 --- a/src/Data/Split.elm +++ b/src/Data/Split.elm @@ -167,11 +167,11 @@ decodeFloat = |> Decode.andThen (\result -> case result of - Ok split -> - Decode.succeed split - Err error -> Decode.fail error + + Ok split -> + Decode.succeed split ) diff --git a/src/Data/Textile/Db.elm b/src/Data/Textile/Db.elm index bbfbe9bbb..0ff783c1c 100644 --- a/src/Data/Textile/Db.elm +++ b/src/Data/Textile/Db.elm @@ -11,12 +11,13 @@ import Data.Textile.Product as Product exposing (Product) import Data.Textile.Query as Query exposing (Query) import Data.Textile.WellKnown as WellKnown exposing (WellKnown) import Json.Decode as Decode +import Result.Extra as RE type alias Db = - { processes : List Process - , examples : List (Example Query) + { examples : List (Example Query) , materials : List Material + , processes : List Process , products : List Product , wellKnown : WellKnown } @@ -29,9 +30,21 @@ buildFromJson exampleProductsJson materialsJson productsJson processesJson = |> Result.mapError Decode.errorToString |> Result.andThen (\processes -> - Result.map4 (Db processes) - (exampleProductsJson |> Example.decodeListFromJsonString Query.decode) - (Decode.decodeString (Material.decodeList processes) materialsJson |> Result.mapError Decode.errorToString) - (Decode.decodeString (Product.decodeList processes) productsJson |> Result.mapError Decode.errorToString) - (WellKnown.load processes) + Ok Db + |> RE.andMap + (exampleProductsJson + |> Example.decodeListFromJsonString Query.decode + ) + |> RE.andMap + (materialsJson + |> Decode.decodeString (Material.decodeList processes) + |> Result.mapError Decode.errorToString + ) + |> RE.andMap (Ok processes) + |> RE.andMap + (productsJson + |> Decode.decodeString (Product.decodeList processes) + |> Result.mapError Decode.errorToString + ) + |> RE.andMap (WellKnown.load processes) ) diff --git a/src/Data/Textile/Economics.elm b/src/Data/Textile/Economics.elm index fe0b3839c..e31e49d7b 100644 --- a/src/Data/Textile/Economics.elm +++ b/src/Data/Textile/Economics.elm @@ -5,7 +5,7 @@ module Data.Textile.Economics exposing , businessFromString , businessToLabel , businessToString - , computeDurabilityIndex + , computeNonPhysicalDurabilityIndex , computeNumberOfReferencesIndex , computeRepairCostIndex , decode @@ -45,12 +45,12 @@ type Price type Business - = -- PME/TPE - SmallBusiness - -- Grande entreprise avec service de réparation - | LargeBusinessWithServices + = -- Grande entreprise avec service de réparation + LargeBusinessWithServices -- Grande entreprise sans service de réparation | LargeBusinessWithoutServices + -- PME/TPE + | SmallBusiness businessFromString : String -> Result String Business @@ -72,35 +72,35 @@ businessFromString string = businessToLabel : Business -> String businessToLabel business = case business of - SmallBusiness -> - "PME/TPE" - LargeBusinessWithServices -> "Grande entreprise avec service de réparation" LargeBusinessWithoutServices -> "Grande entreprise sans service de réparation" + SmallBusiness -> + "PME/TPE" + businessToString : Business -> String businessToString business = case business of - SmallBusiness -> - "small-business" - LargeBusinessWithServices -> "large-business-with-services" LargeBusinessWithoutServices -> "large-business-without-services" + SmallBusiness -> + "small-business" + -computeDurabilityIndex : Economics -> Unit.Durability -computeDurabilityIndex economics = +computeNonPhysicalDurabilityIndex : Economics -> Unit.NonPhysicalDurability +computeNonPhysicalDurabilityIndex economics = let ( minDurability, maxDurability ) = - ( Unit.durabilityToFloat Unit.minDurability - , Unit.durabilityToFloat Unit.maxDurability + ( Unit.nonPhysicalDurabilityToFloat (Unit.minDurability Unit.NonPhysicalDurability) + , Unit.nonPhysicalDurabilityToFloat (Unit.maxDurability Unit.NonPhysicalDurability) ) finalIndex = @@ -119,7 +119,7 @@ computeDurabilityIndex economics = + finalIndex * (maxDurability - minDurability) |> formatIndex - |> Unit.durability + |> Unit.nonPhysicalDurability computeRepairCostIndex : Business -> Price -> Price -> Unit.Ratio @@ -144,12 +144,12 @@ computeRepairCostIndex business price repairCost = in Unit.ratio <| case business of - LargeBusinessWithoutServices -> - repairabilityIndice * 0.67 - LargeBusinessWithServices -> repairabilityIndice * 0.67 + 0.33 + LargeBusinessWithoutServices -> + repairabilityIndice * 0.67 + SmallBusiness -> repairabilityIndice diff --git a/src/Data/Textile/Fabric.elm b/src/Data/Textile/Fabric.elm index 62a308e80..edebd20db 100644 --- a/src/Data/Textile/Fabric.elm +++ b/src/Data/Textile/Fabric.elm @@ -23,12 +23,12 @@ import Json.Encode as Encode type Fabric - = Weaving - | KnittingCircular + = KnittingCircular | KnittingFullyFashioned - | KnittingMix | KnittingIntegral + | KnittingMix | KnittingStraight + | Weaving decode : Decoder Fabric @@ -61,8 +61,8 @@ fabricProcesses = fromString : String -> Result String Fabric fromString string = case string of - "knitting-mix" -> - Ok KnittingMix + "knitting-circular" -> + Ok KnittingCircular "knitting-fully-fashioned" -> Ok KnittingFullyFashioned @@ -70,8 +70,8 @@ fromString string = "knitting-integral" -> Ok KnittingIntegral - "knitting-circular" -> - Ok KnittingCircular + "knitting-mix" -> + Ok KnittingMix "knitting-straight" -> Ok KnittingStraight @@ -124,8 +124,8 @@ getMakingWaste defaultWaste maybeCustomWaste maybeFabric = getProcess : WellKnown -> Fabric -> Process getProcess wellKnown fabric = case fabric of - KnittingMix -> - wellKnown.knittingMix + KnittingCircular -> + wellKnown.knittingCircular KnittingFullyFashioned -> wellKnown.knittingFullyFashioned @@ -133,8 +133,8 @@ getProcess wellKnown fabric = KnittingIntegral -> wellKnown.knittingSeamless - KnittingCircular -> - wellKnown.knittingCircular + KnittingMix -> + wellKnown.knittingMix KnittingStraight -> wellKnown.knittingStraight @@ -156,8 +156,8 @@ isKnitted fabric = toLabel : Fabric -> String toLabel fabricProcess = case fabricProcess of - KnittingMix -> - "Tricotage moyen (par défaut)" + KnittingCircular -> + "Tricotage Circulaire" KnittingFullyFashioned -> "Tricotage Fully fashioned / Seamless" @@ -165,8 +165,8 @@ toLabel fabricProcess = KnittingIntegral -> "Tricotage Intégral / Whole garment" - KnittingCircular -> - "Tricotage Circulaire" + KnittingMix -> + "Tricotage moyen (par défaut)" KnittingStraight -> "Tricotage Rectiligne" @@ -178,8 +178,8 @@ toLabel fabricProcess = toString : Fabric -> String toString fabricProcess = case fabricProcess of - KnittingMix -> - "knitting-mix" + KnittingCircular -> + "knitting-circular" KnittingFullyFashioned -> "knitting-fully-fashioned" @@ -187,8 +187,8 @@ toString fabricProcess = KnittingIntegral -> "knitting-integral" - KnittingCircular -> - "knitting-circular" + KnittingMix -> + "knitting-mix" KnittingStraight -> "knitting-straight" diff --git a/src/Data/Textile/Formula.elm b/src/Data/Textile/Formula.elm index caa274542..6ae45d96c 100644 --- a/src/Data/Textile/Formula.elm +++ b/src/Data/Textile/Formula.elm @@ -37,26 +37,33 @@ import Quantity import Volume exposing (Volume) +type alias StepValues = + { heat : Energy + , impacts : Impacts + , kwh : Energy + } + + -- Waste {-| Compute source mass needed and waste generated by the operation. -} -genericWaste : Unit.Ratio -> Mass -> { waste : Mass, mass : Mass } +genericWaste : Unit.Ratio -> Mass -> { mass : Mass, waste : Mass } genericWaste processWaste baseMass = let waste = baseMass |> Quantity.multiplyBy (Unit.ratioToFloat processWaste) in - { waste = waste, mass = baseMass |> Quantity.plus waste } + { 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 -> { waste : Mass, mass : Mass } +makingWaste : Split -> Mass -> { mass : Mass, waste : Mass } makingWaste pcrWaste baseMass = let mass = @@ -64,7 +71,7 @@ makingWaste pcrWaste baseMass = baseMass |> Quantity.divideBy (Split.toFloat (Split.complement pcrWaste)) in - { waste = Quantity.minus baseMass mass, mass = mass } + { mass = mass, waste = Quantity.minus baseMass mass } {-| Compute source material mass needed and deadstock generated by the operation, according to @@ -98,10 +105,10 @@ pureMaterialImpacts impacts process mass = recycledMaterialImpacts : Impacts - -> { recycledProcess : Process, nonRecycledProcess : Process, cffData : CFFData } + -> { cffData : CFFData, nonRecycledProcess : Process, recycledProcess : Process } -> Mass -> Impacts -recycledMaterialImpacts impacts { recycledProcess, nonRecycledProcess, cffData } outputMass = +recycledMaterialImpacts impacts { cffData, nonRecycledProcess, recycledProcess } outputMass = let { manufacturerAllocation, recycledQualityRatio } = cffData @@ -126,16 +133,17 @@ recycledMaterialImpacts impacts { recycledProcess, nonRecycledProcess, cffData } spinningImpacts : Impacts - -> { spinningKwh : Energy, countryElecProcess : Process } - -> { kwh : Energy, impacts : Impacts } -spinningImpacts impacts { spinningKwh, countryElecProcess } = - { kwh = spinningKwh + -> { countryElecProcess : Process, spinningKwh : Energy } + -> StepValues +spinningImpacts impacts { countryElecProcess, spinningKwh } = + { heat = Quantity.zero , impacts = impacts |> Impact.mapImpacts (\trigram _ -> spinningKwh |> Unit.forKWh (Process.getImpact trigram countryElecProcess) ) + , kwh = spinningKwh } @@ -145,7 +153,7 @@ dyeingImpacts : -> Process -- Outbound: country heat impact -> Process -- Outbound: country electricity impact -> Mass - -> { heat : Energy, kwh : Energy, impacts : Impacts } + -> StepValues dyeingImpacts impacts dyeingProcess heatProcess elecProcess baseMass = let heatMJ = @@ -159,7 +167,6 @@ dyeingImpacts impacts dyeingProcess heatProcess elecProcess baseMass = |> Energy.megajoules in { heat = heatMJ - , kwh = kwh , impacts = impacts |> Impact.mapImpacts @@ -170,21 +177,22 @@ dyeingImpacts impacts dyeingProcess heatProcess elecProcess baseMass = , kwh |> Unit.forKWh (Process.getImpact trigram elecProcess) ] ) + , kwh = kwh } printingImpacts : Impacts -> - { printingProcess : Process -- Inbound: Printing process + { elecProcess : Process -- Outbound: country electricity impact , heatProcess : Process -- Outbound: country heat impact - , elecProcess : Process -- Outbound: country electricity impact - , surfaceMass : Unit.SurfaceMass + , printingProcess : Process -- Inbound: Printing process , ratio : Split + , surfaceMass : Unit.SurfaceMass } -> Mass - -> { heat : Energy, kwh : Energy, impacts : Impacts } -printingImpacts impacts { printingProcess, heatProcess, elecProcess, surfaceMass, ratio } baseMass = + -> StepValues +printingImpacts impacts { elecProcess, heatProcess, printingProcess, ratio, surfaceMass } baseMass = let surface = Unit.surfaceMassToSurface surfaceMass baseMass @@ -199,7 +207,6 @@ printingImpacts impacts { printingProcess, heatProcess, elecProcess, surfaceMass ) in { heat = heatMJ - , kwh = kwh , impacts = impacts |> Impact.mapImpacts @@ -210,19 +217,20 @@ printingImpacts impacts { printingProcess, heatProcess, elecProcess, surfaceMass , kwh |> Unit.forKWh (Process.getImpact trigram elecProcess) ] ) + , kwh = kwh } finishingImpacts : Impacts -> - { finishingProcess : Process -- Inbound: Printing process + { elecProcess : Process -- Outbound: country electricity impact + , finishingProcess : Process -- Inbound: Printing process , heatProcess : Process -- Outbound: country heat impact - , elecProcess : Process -- Outbound: country electricity impact } -> Mass - -> { heat : Energy, kwh : Energy, impacts : Impacts } -finishingImpacts impacts { finishingProcess, heatProcess, elecProcess } baseMass = + -> StepValues +finishingImpacts impacts { elecProcess, finishingProcess, heatProcess } baseMass = let ( heatMJ, kwh ) = ( Quantity.multiplyBy (Mass.inKilograms baseMass) finishingProcess.heat @@ -230,7 +238,6 @@ finishingImpacts impacts { finishingProcess, heatProcess, elecProcess } baseMass ) in { heat = heatMJ - , kwh = kwh , impacts = impacts |> Impact.mapImpacts @@ -241,6 +248,7 @@ finishingImpacts impacts { finishingProcess, heatProcess, elecProcess } baseMass , kwh |> Unit.forKWh (Process.getImpact trigram elecProcess) ] ) + , kwh = kwh } @@ -264,12 +272,12 @@ getAquaticPollutionRealRatio scenario = bleachingImpacts : Impacts -> - { bleachingProcess : Process -- Inbound: Bleaching process - , aquaticPollutionScenario : Country.AquaticPollutionScenario + { aquaticPollutionScenario : Country.AquaticPollutionScenario + , bleachingProcess : Process -- Inbound: Bleaching process } -> Mass -> Impacts -bleachingImpacts impacts { bleachingProcess, aquaticPollutionScenario } baseMass = +bleachingImpacts impacts { aquaticPollutionScenario, bleachingProcess } baseMass = impacts |> Impact.mapImpacts (\trigram _ -> @@ -282,13 +290,13 @@ bleachingImpacts impacts { bleachingProcess, aquaticPollutionScenario } baseMass materialDyeingToxicityImpacts : Impacts -> - { dyeingToxicityProcess : Process -- Inbound: dyeing process - , aquaticPollutionScenario : Country.AquaticPollutionScenario + { aquaticPollutionScenario : Country.AquaticPollutionScenario + , dyeingToxicityProcess : Process -- Inbound: dyeing process } -> Mass -> Split -> Impacts -materialDyeingToxicityImpacts impacts { dyeingToxicityProcess, aquaticPollutionScenario } baseMass split = +materialDyeingToxicityImpacts impacts { aquaticPollutionScenario, dyeingToxicityProcess } baseMass split = impacts |> Impact.mapImpacts (\trigram _ -> @@ -302,13 +310,13 @@ materialDyeingToxicityImpacts impacts { dyeingToxicityProcess, aquaticPollutionS materialPrintingToxicityImpacts : Impacts -> - { printingToxicityProcess : Process -- Inbound: printing process - , aquaticPollutionScenario : Country.AquaticPollutionScenario + { aquaticPollutionScenario : Country.AquaticPollutionScenario + , printingToxicityProcess : Process -- Inbound: printing process } -> Split -> Mass -> Impacts -materialPrintingToxicityImpacts impacts { printingToxicityProcess, aquaticPollutionScenario } split baseMass = +materialPrintingToxicityImpacts impacts { aquaticPollutionScenario, printingToxicityProcess } split baseMass = impacts |> Impact.mapImpacts (\trigram _ -> @@ -322,14 +330,14 @@ materialPrintingToxicityImpacts impacts { printingToxicityProcess, aquaticPollut makingImpacts : Impacts -> - { makingComplexity : MakingComplexity - , fadingProcess : Maybe Process - , countryElecProcess : Process + { countryElecProcess : Process , countryHeatProcess : Process + , fadingProcess : Maybe Process + , makingComplexity : MakingComplexity } -> Mass - -> { kwh : Energy, heat : Energy, impacts : Impacts } -makingImpacts impacts { makingComplexity, fadingProcess, countryElecProcess, countryHeatProcess } outputMass = + -> StepValues +makingImpacts impacts { countryElecProcess, countryHeatProcess, fadingProcess, makingComplexity } outputMass = -- Note: Fading, when enabled, is applied at the Making step because -- it can only be applied on finished products (using step output mass). -- Also: @@ -354,8 +362,7 @@ makingImpacts impacts { makingComplexity, fadingProcess, countryElecProcess, cou elec = Quantity.multiplyBy (MakingComplexity.toDuration makingComplexity |> Duration.inMinutes) kWhPerMinute in - { kwh = Quantity.sum [ elec, fadingElec ] - , heat = fadingHeat + { heat = fadingHeat , impacts = impacts |> Impact.mapImpacts @@ -378,35 +385,37 @@ makingImpacts impacts { makingComplexity, fadingProcess, countryElecProcess, cou |> Unit.forMJ (Process.getImpact trigram countryHeatProcess) ] ) + , kwh = Quantity.sum [ elec, fadingElec ] } knittingImpacts : Impacts - -> { elec : Energy, countryElecProcess : Process } + -> { countryElecProcess : Process, elec : Energy } -> Mass -> - { kwh : Energy - , threadDensity : Maybe Unit.ThreadDensity + { impacts : Impacts + , kwh : Energy , picking : Maybe Unit.PickPerMeter - , impacts : Impacts + , threadDensity : Maybe Unit.ThreadDensity } -knittingImpacts impacts { elec, countryElecProcess } baseMass = +knittingImpacts impacts { countryElecProcess, elec } baseMass = let electricityKWh = Energy.kilowattHours (Mass.inKilograms baseMass * Energy.inKilowattHours elec) in - { kwh = electricityKWh - , threadDensity = Nothing - , picking = Nothing - , impacts = + -- FIXME: why don't we use threadDensity and picking here? + { impacts = impacts |> Impact.mapImpacts (\trigram _ -> electricityKWh |> Unit.forKWh (Process.getImpact trigram countryElecProcess) ) + , kwh = electricityKWh + , picking = Nothing + , threadDensity = Nothing } @@ -420,10 +429,10 @@ weavingImpacts : , yarnSize : Unit.YarnSize } -> - { kwh : Energy - , threadDensity : Maybe Unit.ThreadDensity + { impacts : Impacts + , kwh : Energy , picking : Maybe Unit.PickPerMeter - , impacts : Impacts + , threadDensity : Maybe Unit.ThreadDensity } weavingImpacts impacts { countryElecProcess, outputMass, pickingElec, surfaceMass, yarnSize } = -- Methodology: https://fabrique-numerique.gitbook.io/ecobalyse/textile/etapes-du-cycle-de-vie/tricotage-tissage @@ -443,16 +452,16 @@ weavingImpacts impacts { countryElecProcess, outputMass, pickingElec, surfaceMas * Unit.pickPerMeterToFloat picking |> Energy.kilowattHours in - { kwh = electricityKWh - , threadDensity = Just threadDensity - , picking = Just picking - , impacts = + { impacts = impacts |> Impact.mapImpacts (\trigram _ -> electricityKWh |> Unit.forKWh (Process.getImpact trigram countryElecProcess) ) + , kwh = electricityKWh + , picking = Just picking + , threadDensity = Just threadDensity } @@ -488,14 +497,14 @@ computePicking threadDensity outputSurface = useImpacts : Impacts -> - { useNbCycles : Int + { countryElecProcess : Process , ironingElec : Energy , nonIroningProcess : Process - , countryElecProcess : Process + , useNbCycles : Int } -> Mass - -> { kwh : Energy, impacts : Impacts } -useImpacts impacts { useNbCycles, ironingElec, nonIroningProcess, countryElecProcess } baseMass = + -> StepValues +useImpacts impacts { countryElecProcess, ironingElec, nonIroningProcess, useNbCycles } baseMass = let totalEnergy = -- Note: Ironing is expressed per-item, non-ironing is mass-depdendent @@ -506,7 +515,7 @@ useImpacts impacts { useNbCycles, ironingElec, nonIroningProcess, countryElecPro |> Quantity.sum |> Quantity.multiplyBy (toFloat useNbCycles) in - { kwh = totalEnergy + { heat = Quantity.zero , impacts = impacts |> Impact.mapImpacts @@ -519,21 +528,22 @@ useImpacts impacts { useNbCycles, ironingElec, nonIroningProcess, countryElecPro |> Quantity.multiplyBy (toFloat useNbCycles) ] ) + , kwh = totalEnergy } endOfLifeImpacts : Impacts -> - { volume : Volume - , passengerCar : Process + { countryElecProcess : Process , endOfLife : Process - , countryElecProcess : Process , heatProcess : Process + , passengerCar : Process + , volume : Volume } -> Mass - -> { kwh : Energy, heat : Energy, impacts : Impacts } -endOfLifeImpacts impacts { volume, passengerCar, endOfLife, countryElecProcess, heatProcess } baseMass = + -> StepValues +endOfLifeImpacts impacts { countryElecProcess, endOfLife, heatProcess, passengerCar, volume } baseMass = -- Notes: -- - passengerCar is expressed per-item -- - endOfLife is mass-dependent @@ -559,8 +569,7 @@ endOfLifeImpacts impacts { volume, passengerCar, endOfLife, countryElecProcess, ] ) in - { kwh = elecEnergy - , heat = heatEnergy + { heat = heatEnergy , impacts = impacts |> Impact.mapImpacts @@ -576,6 +585,7 @@ endOfLifeImpacts impacts { volume, passengerCar, endOfLife, countryElecProcess, |> Unit.forKg (Process.getImpact trigram endOfLife) ] ) + , kwh = elecEnergy } @@ -593,7 +603,7 @@ transportRatio airTransportRatio ({ road, sea, air } as transport) = Split.complement roadRatio in { transport - | road = road |> Quantity.multiplyBy (Split.apply (Split.toFloat roadRatio) (Split.complement airTransportRatio)) + | air = air |> Quantity.multiplyBy (Split.toFloat airTransportRatio) + , road = road |> Quantity.multiplyBy (Split.apply (Split.toFloat roadRatio) (Split.complement airTransportRatio)) , sea = sea |> Quantity.multiplyBy (Split.apply (Split.toFloat seaRatio) (Split.complement airTransportRatio)) - , air = air |> Quantity.multiplyBy (Split.toFloat airTransportRatio) } diff --git a/src/Data/Textile/Inputs.elm b/src/Data/Textile/Inputs.elm index 1cfe22fab..bb667d593 100644 --- a/src/Data/Textile/Inputs.elm +++ b/src/Data/Textile/Inputs.elm @@ -39,51 +39,52 @@ import Views.Format as Format type alias MaterialInput = - { material : Material + { country : Maybe Country + , material : Material , share : Split , spinning : Maybe Spinning - , country : Maybe Country } type alias Inputs = - { mass : Mass - , materials : List MaterialInput - , product : Product + { airTransportRatio : Maybe Split + , business : Maybe Economics.Business + , countryDistribution : Country + , countryDyeing : Country + , countryEndOfLife : Country + , countryFabric : Country + , countryMaking : Country -- TODO: countryMaterial isn't used anymore, but we still need it because `countryList` uses it, -- which in turn is used to build the lifecycle, which needs a country for each step. , countryMaterial : Country , countrySpinning : Country - , countryFabric : Country - , countryDyeing : Country - , countryMaking : Country - , countryDistribution : Country , countryUse : Country - , countryEndOfLife : Country - , airTransportRatio : Maybe Split - , makingWaste : Maybe Split - , makingDeadStock : Maybe Split - , makingComplexity : Maybe MakingComplexity - , yarnSize : Maybe Unit.YarnSize - , surfaceMass : Maybe Unit.SurfaceMass - , fabricProcess : Maybe Fabric , disabledSteps : List Label - , fading : Maybe Bool , dyeingMedium : Maybe DyeingMedium - , printing : Maybe Printing - , business : Maybe Economics.Business + , fabricProcess : Maybe Fabric + , fading : Maybe Bool + , makingComplexity : Maybe MakingComplexity + , makingDeadStock : Maybe Split + , makingWaste : Maybe Split + , mass : Mass + , materials : List MaterialInput , numberOfReferences : Maybe Int + , physicalDurability : Maybe Unit.PhysicalDurability , price : Maybe Economics.Price + , printing : Maybe Printing + , product : Product + , surfaceMass : Maybe Unit.SurfaceMass , traceability : Maybe Bool , upcycled : Bool + , yarnSize : Maybe Unit.YarnSize } fromMaterialQuery : List Material -> List Country -> List MaterialQuery -> Result String (List MaterialInput) fromMaterialQuery materials countries = List.map - (\{ id, share, spinning, country } -> + (\{ country, id, share, spinning } -> let countryResult = case country of @@ -96,10 +97,10 @@ fromMaterialQuery materials countries = in Result.map2 (\material_ country_ -> - { material = material_ + { country = country_ + , material = material_ , share = share , spinning = spinning - , country = country_ } ) (Material.findById id materials) @@ -111,11 +112,11 @@ fromMaterialQuery materials countries = toMaterialQuery : List MaterialInput -> List MaterialQuery toMaterialQuery = List.map - (\{ material, share, spinning, country } -> - { id = material.id + (\{ country, material, share, spinning } -> + { country = country |> Maybe.andThen (.code >> toQueryCountryCode) + , id = material.id , share = share , spinning = spinning - , country = country |> Maybe.andThen (.code >> toQueryCountryCode) } ) @@ -165,66 +166,68 @@ fromQuery { countries, textile } query = fallbackResult in Ok Inputs - |> RE.andMap (Ok query.mass) - |> RE.andMap materials_ - |> RE.andMap (textile.products |> Product.findById query.product) + |> RE.andMap (Ok query.airTransportRatio) + |> RE.andMap (Ok query.business) + -- The distribution country is always France + |> RE.andMap franceResult + |> RE.andMap (getCountryResult unknownCountryResult query.countryDyeing) + -- The end of life country is always France + |> RE.andMap franceResult + |> RE.andMap (getCountryResult unknownCountryResult query.countryFabric) + |> RE.andMap (getCountryResult unknownCountryResult query.countryMaking) -- Material country is constrained to be the first material's default country |> RE.andMap mainMaterialCountry -- Spinning country is either provided by query or fallbacks to material's default -- country, making the parameter optional |> RE.andMap (getCountryResult mainMaterialCountry query.countrySpinning) - |> RE.andMap (getCountryResult unknownCountryResult query.countryFabric) - |> RE.andMap (getCountryResult unknownCountryResult query.countryDyeing) - |> RE.andMap (getCountryResult unknownCountryResult query.countryMaking) - -- The distribution country is always France - |> RE.andMap franceResult -- The use country is always France |> RE.andMap franceResult - -- The end of life country is always France - |> RE.andMap franceResult - |> RE.andMap (Ok query.airTransportRatio) - |> RE.andMap (Ok query.makingWaste) - |> RE.andMap (Ok query.makingDeadStock) - |> RE.andMap (Ok query.makingComplexity) - |> RE.andMap (Ok query.yarnSize) - |> RE.andMap (Ok query.surfaceMass) - |> RE.andMap (Ok query.fabricProcess) |> RE.andMap (Ok query.disabledSteps) - |> RE.andMap (Ok query.fading) |> RE.andMap (Ok query.dyeingMedium) - |> RE.andMap (Ok query.printing) - |> RE.andMap (Ok query.business) + |> RE.andMap (Ok query.fabricProcess) + |> RE.andMap (Ok query.fading) + |> RE.andMap (Ok query.makingComplexity) + |> RE.andMap (Ok query.makingDeadStock) + |> RE.andMap (Ok query.makingWaste) + |> RE.andMap (Ok query.mass) + |> RE.andMap materials_ |> RE.andMap (Ok query.numberOfReferences) + |> RE.andMap (Ok query.physicalDurability) |> RE.andMap (Ok query.price) + |> RE.andMap (Ok query.printing) + |> RE.andMap (textile.products |> Product.findById query.product) + |> RE.andMap (Ok query.surfaceMass) |> RE.andMap (Ok query.traceability) |> RE.andMap (Ok query.upcycled) + |> RE.andMap (Ok query.yarnSize) toQuery : Inputs -> Query toQuery inputs = - { mass = inputs.mass - , materials = toMaterialQuery inputs.materials - , product = inputs.product.id - , countrySpinning = toQueryCountryCode inputs.countrySpinning.code - , countryFabric = toQueryCountryCode inputs.countryFabric.code + { airTransportRatio = inputs.airTransportRatio + , business = inputs.business , countryDyeing = toQueryCountryCode inputs.countryDyeing.code + , countryFabric = toQueryCountryCode inputs.countryFabric.code , countryMaking = toQueryCountryCode inputs.countryMaking.code - , airTransportRatio = inputs.airTransportRatio - , makingWaste = inputs.makingWaste - , makingDeadStock = inputs.makingDeadStock - , makingComplexity = inputs.makingComplexity - , yarnSize = inputs.yarnSize - , surfaceMass = inputs.surfaceMass - , fabricProcess = inputs.fabricProcess + , countrySpinning = toQueryCountryCode inputs.countrySpinning.code , disabledSteps = inputs.disabledSteps - , fading = inputs.fading , dyeingMedium = inputs.dyeingMedium - , printing = inputs.printing - , business = inputs.business + , fabricProcess = inputs.fabricProcess + , fading = inputs.fading + , makingComplexity = inputs.makingComplexity + , makingDeadStock = inputs.makingDeadStock + , makingWaste = inputs.makingWaste + , mass = inputs.mass + , materials = toMaterialQuery inputs.materials , numberOfReferences = inputs.numberOfReferences + , physicalDurability = inputs.physicalDurability , price = inputs.price + , printing = inputs.printing + , product = inputs.product.id + , surfaceMass = inputs.surfaceMass , traceability = inputs.traceability , upcycled = inputs.upcycled + , yarnSize = inputs.yarnSize } @@ -254,6 +257,13 @@ stepsToStrings inputs = else "" ) + ++ (case inputs.physicalDurability of + Just physicalDurability -> + ", durabilité physique " ++ String.fromFloat (Unit.physicalDurabilityToFloat physicalDurability) + + Nothing -> + "" + ) , Format.kgToString inputs.mass ] , ifStepEnabled Label.Material @@ -327,7 +337,7 @@ materialsToString materials = materials |> List.filter (\{ share } -> Split.toFloat share > 0) |> List.map - (\{ material, share, country } -> + (\{ country, material, share } -> let countryName = country @@ -344,7 +354,7 @@ materialsToString materials = makingOptionsToString : Inputs -> String -makingOptionsToString { makingWaste, makingDeadStock, makingComplexity, airTransportRatio, fading } = +makingOptionsToString { airTransportRatio, fading, makingComplexity, makingDeadStock, makingWaste } = [ makingWaste |> Maybe.map (Split.toPercentString 0 >> (\s -> s ++ "\u{202F}% de perte")) , makingDeadStock @@ -456,7 +466,7 @@ getOutOfEuropeEOLComplement { mass, materials } = computeMaterialTransport : Distances -> Country.Code -> MaterialInput -> Transport -computeMaterialTransport distances nextCountryCode { material, country, share } = +computeMaterialTransport distances nextCountryCode { country, material, share } = if share /= Split.zero then let emptyImpacts = @@ -482,37 +492,38 @@ isFabricOfType fabric { fabricProcess, product } = encode : Inputs -> Encode.Value encode inputs = Encode.object - [ ( "mass", Encode.float (Mass.inKilograms inputs.mass) ) - , ( "materials", Encode.list encodeMaterialInput inputs.materials ) - , ( "product", Product.encode inputs.product ) - , ( "countryFabric", Country.encode inputs.countryFabric ) + [ ( "airTransportRatio", inputs.airTransportRatio |> Maybe.map Split.encodeFloat |> Maybe.withDefault Encode.null ) + , ( "business", inputs.business |> Maybe.map Economics.encodeBusiness |> Maybe.withDefault Encode.null ) , ( "countryDyeing", Country.encode inputs.countryDyeing ) + , ( "countryFabric", Country.encode inputs.countryFabric ) , ( "countryMaking", Country.encode inputs.countryMaking ) - , ( "airTransportRatio", inputs.airTransportRatio |> Maybe.map Split.encodeFloat |> Maybe.withDefault Encode.null ) - , ( "makingWaste", inputs.makingWaste |> Maybe.map Split.encodeFloat |> Maybe.withDefault Encode.null ) - , ( "makingDeadStock", inputs.makingDeadStock |> Maybe.map Split.encodeFloat |> Maybe.withDefault Encode.null ) - , ( "makingComplexity", inputs.makingComplexity |> Maybe.map (MakingComplexity.toString >> Encode.string) |> Maybe.withDefault Encode.null ) - , ( "yarnSize", inputs.yarnSize |> Maybe.map Unit.encodeYarnSize |> Maybe.withDefault Encode.null ) - , ( "surfaceMass", inputs.surfaceMass |> Maybe.map Unit.encodeSurfaceMass |> Maybe.withDefault Encode.null ) - , ( "fabricProcess", inputs.fabricProcess |> Maybe.map Fabric.encode |> Maybe.withDefault Encode.null ) , ( "disabledSteps", Encode.list Label.encode inputs.disabledSteps ) - , ( "fading", inputs.fading |> Maybe.map Encode.bool |> Maybe.withDefault Encode.null ) , ( "dyeingMedium", inputs.dyeingMedium |> Maybe.map DyeingMedium.encode |> Maybe.withDefault Encode.null ) - , ( "printing", inputs.printing |> Maybe.map Printing.encode |> Maybe.withDefault Encode.null ) - , ( "business", inputs.business |> Maybe.map Economics.encodeBusiness |> Maybe.withDefault Encode.null ) + , ( "fabricProcess", inputs.fabricProcess |> Maybe.map Fabric.encode |> Maybe.withDefault Encode.null ) + , ( "fading", inputs.fading |> Maybe.map Encode.bool |> Maybe.withDefault Encode.null ) + , ( "makingComplexity", inputs.makingComplexity |> Maybe.map (MakingComplexity.toString >> Encode.string) |> Maybe.withDefault Encode.null ) + , ( "makingDeadStock", inputs.makingDeadStock |> Maybe.map Split.encodeFloat |> Maybe.withDefault Encode.null ) + , ( "makingWaste", inputs.makingWaste |> Maybe.map Split.encodeFloat |> Maybe.withDefault Encode.null ) + , ( "mass", Encode.float (Mass.inKilograms inputs.mass) ) + , ( "materials", Encode.list encodeMaterialInput inputs.materials ) , ( "numberOfReferences", inputs.numberOfReferences |> Maybe.map Encode.int |> Maybe.withDefault Encode.null ) + , ( "physicalDurability", inputs.physicalDurability |> Maybe.map Unit.encodePhysicalDurability |> Maybe.withDefault Encode.null ) , ( "price", inputs.price |> Maybe.map Economics.encodePrice |> Maybe.withDefault Encode.null ) + , ( "printing", inputs.printing |> Maybe.map Printing.encode |> Maybe.withDefault Encode.null ) + , ( "product", Product.encode inputs.product ) + , ( "surfaceMass", inputs.surfaceMass |> Maybe.map Unit.encodeSurfaceMass |> Maybe.withDefault Encode.null ) , ( "traceability", inputs.traceability |> Maybe.map Encode.bool |> Maybe.withDefault Encode.null ) , ( "upcycled", Encode.bool inputs.upcycled ) + , ( "yarnSize", inputs.yarnSize |> Maybe.map Unit.encodeYarnSize |> Maybe.withDefault Encode.null ) ] encodeMaterialInput : MaterialInput -> Encode.Value encodeMaterialInput v = - [ ( "material", Material.encode v.material |> Just ) + [ ( "country", v.country |> Maybe.map (.code >> Country.encodeCode) ) + , ( "material", Material.encode v.material |> Just ) , ( "share", Split.encodeFloat v.share |> Just ) , ( "spinning", v.spinning |> Maybe.map Spinning.encode ) - , ( "country", v.country |> Maybe.map (.code >> Country.encodeCode) ) ] |> List.filterMap (\( key, maybeVal ) -> maybeVal |> Maybe.map (\val -> ( key, val ))) |> Encode.object diff --git a/src/Data/Textile/LifeCycle.elm b/src/Data/Textile/LifeCycle.elm index adcf0d0c9..a2edabdf5 100644 --- a/src/Data/Textile/LifeCycle.elm +++ b/src/Data/Textile/LifeCycle.elm @@ -55,9 +55,7 @@ computeTotalTransportImpacts = Array.foldl (\{ transport } acc -> { acc - | road = acc.road |> Quantity.plus transport.road - , sea = acc.sea |> Quantity.plus transport.sea - , air = acc.air |> Quantity.plus transport.air + | air = acc.air |> Quantity.plus transport.air , impacts = acc.impacts |> Impact.mapImpacts @@ -67,6 +65,8 @@ computeTotalTransportImpacts = , Impact.getImpact trigram transport.impacts ] ) + , road = acc.road |> Quantity.plus transport.road + , sea = acc.sea |> Quantity.plus transport.sea } ) (Transport.default Impact.empty) @@ -130,10 +130,10 @@ init { textile } inputs = |> List.map2 (\( label, editable ) country -> Step.create - { label = label + { country = country , editable = editable - , country = country , enabled = not (List.member label inputs.disabledSteps) + , label = label } ) [ ( Label.Material, False ) diff --git a/src/Data/Textile/MakingComplexity.elm b/src/Data/Textile/MakingComplexity.elm index 3639693b3..d125378da 100644 --- a/src/Data/Textile/MakingComplexity.elm +++ b/src/Data/Textile/MakingComplexity.elm @@ -13,101 +13,101 @@ import Json.Decode.Extra as DE type MakingComplexity - = VeryHigh - | High - | Medium + = High | Low - | VeryLow + | Medium | NotApplicable + | VeryHigh + | VeryLow toDuration : MakingComplexity -> Duration toDuration makingComplexity = case makingComplexity of - VeryHigh -> - Duration.minutes 120 - High -> Duration.minutes 60 - Medium -> - Duration.minutes 30 - Low -> Duration.minutes 15 - VeryLow -> - Duration.minutes 5 + Medium -> + Duration.minutes 30 NotApplicable -> Duration.minutes 0 + VeryHigh -> + Duration.minutes 120 + + VeryLow -> + Duration.minutes 5 + toLabel : MakingComplexity -> String toLabel makingComplexity = case makingComplexity of - VeryHigh -> - "Très élevée" - High -> "Elevée" - Medium -> - "Moyenne" - Low -> "Faible" - VeryLow -> - "Très faible" + Medium -> + "Moyenne" NotApplicable -> "Non applicable" + VeryHigh -> + "Très élevée" + + VeryLow -> + "Très faible" + toString : MakingComplexity -> String toString makingComplexity = case makingComplexity of - VeryHigh -> - "very-high" - High -> "high" - Medium -> - "medium" - Low -> "low" - VeryLow -> - "very-low" + Medium -> + "medium" NotApplicable -> "non-applicable" + VeryHigh -> + "very-high" + + VeryLow -> + "very-low" + fromString : String -> Result String MakingComplexity fromString str = case str of - "very-high" -> - Ok VeryHigh - "high" -> Ok High - "medium" -> - Ok Medium - "low" -> Ok Low - "very-low" -> - Ok VeryLow + "medium" -> + Ok Medium "not-applicable" -> Ok NotApplicable + "very-high" -> + Ok VeryHigh + + "very-low" -> + Ok VeryLow + _ -> Err ("Type de complexité de fabrication inconnu\u{00A0}: " ++ str) diff --git a/src/Data/Textile/Material.elm b/src/Data/Textile/Material.elm index 094f26c01..8e5e5d169 100644 --- a/src/Data/Textile/Material.elm +++ b/src/Data/Textile/Material.elm @@ -20,16 +20,16 @@ import Json.Encode as Encode type alias Material = - { id : Id + { cffData : Maybe CFFData + , defaultCountry : Country.Code -- Default country for Material and Spinning steps + , geographicOrigin : String -- A textual information about the geographic origin of the material + , id : Id + , materialProcess : Process , name : String - , shortName : String , origin : Origin - , materialProcess : Process - , recycledProcess : Maybe Process , recycledFrom : Maybe Id - , geographicOrigin : String -- A textual information about the geographic origin of the material - , defaultCountry : Country.Code -- Default country for Material and Spinning steps - , cffData : Maybe CFFData + , recycledProcess : Maybe Process + , shortName : String } @@ -76,16 +76,16 @@ findById id = decode : List Process -> Decoder Material decode processes = Decode.succeed Material + |> JDP.required "cff" (Decode.maybe decodeCFFData) + |> JDP.required "defaultCountry" (Decode.string |> Decode.map Country.codeFromString) + |> JDP.required "geographicOrigin" Decode.string |> JDP.required "id" (Decode.map Id Decode.string) + |> JDP.required "materialProcessUuid" (Process.decodeFromUuid processes) |> JDP.required "name" Decode.string - |> JDP.required "shortName" Decode.string |> JDP.required "origin" Origin.decode - |> JDP.required "materialProcessUuid" (Process.decodeFromUuid processes) - |> JDP.required "recycledProcessUuid" (Decode.maybe (Process.decodeFromUuid processes)) |> JDP.required "recycledFrom" (Decode.maybe (Decode.map Id Decode.string)) - |> JDP.required "geographicOrigin" Decode.string - |> JDP.required "defaultCountry" (Decode.string |> Decode.map Country.codeFromString) - |> JDP.required "cff" (Decode.maybe decodeCFFData) + |> JDP.required "recycledProcessUuid" (Decode.maybe (Process.decodeFromUuid processes)) + |> JDP.required "shortName" Decode.string decodeCFFData : Decoder CFFData diff --git a/src/Data/Textile/Material/Spinning.elm b/src/Data/Textile/Material/Spinning.elm index f64e4498d..05709bd1c 100644 --- a/src/Data/Textile/Material/Spinning.elm +++ b/src/Data/Textile/Material/Spinning.elm @@ -22,8 +22,8 @@ import Mass exposing (Mass) type Spinning = Conventional - | Unconventional | Synthetic + | Unconventional type alias ProcessData = @@ -36,12 +36,12 @@ fromString string = "ConventionalSpinning" -> Ok Conventional - "UnconventionalSpinning" -> - Ok Unconventional - "SyntheticSpinning" -> Ok Synthetic + "UnconventionalSpinning" -> + Ok Unconventional + other -> Err <| "Le procédé de filature ou filage " ++ other ++ " n'est pas valide" @@ -52,12 +52,12 @@ toString spinning = Conventional -> "ConventionalSpinning" - Unconventional -> - "UnconventionalSpinning" - Synthetic -> "SyntheticSpinning" + Unconventional -> + "UnconventionalSpinning" + toLabel : Spinning -> String toLabel spinning = @@ -65,12 +65,12 @@ toLabel spinning = Conventional -> "Filature conventionnelle" - Unconventional -> - "Filature non conventionnelle" - Synthetic -> "Filage" + Unconventional -> + "Filature non conventionnelle" + decode : Decoder Spinning decode = @@ -83,13 +83,13 @@ encode = toString >> Encode.string -processesData : { conventional : ProcessData, unconventional : ProcessData, synthetic : ProcessData } +processesData : { conventional : ProcessData, synthetic : ProcessData, unconventional : ProcessData } processesData = -- See https://fabrique-numerique.gitbook.io/ecobalyse/textile/etapes-du-cycle-de-vie/etape-2-fabrication-du-fil-new-draft#consommation-delectricite -- and https://fabrique-numerique.gitbook.io/ecobalyse/textile/etapes-du-cycle-de-vie/etape-2-fabrication-du-fil-new-draft#taux-de-pertes { conventional = { normalization = 4, waste = Split.fromPercent 12 |> Result.withDefault Split.zero } - , unconventional = { normalization = 2, waste = Split.fromPercent 12 |> Result.withDefault Split.zero } , synthetic = { normalization = 1.5, waste = Split.fromPercent 3 |> Result.withDefault Split.zero } + , unconventional = { normalization = 2, waste = Split.fromPercent 12 |> Result.withDefault Split.zero } } @@ -123,12 +123,12 @@ normalization spinning = Conventional -> processesData.conventional.normalization - Unconventional -> - processesData.unconventional.normalization - Synthetic -> processesData.synthetic.normalization + Unconventional -> + processesData.unconventional.normalization + waste : Spinning -> Split waste spinning = @@ -136,12 +136,12 @@ waste spinning = Conventional -> processesData.conventional.waste - Unconventional -> - processesData.unconventional.waste - Synthetic -> processesData.synthetic.waste + Unconventional -> + processesData.unconventional.waste + getElec : Mass -> Unit.YarnSize -> Spinning -> Float getElec mass yarnSize spinning = diff --git a/src/Data/Textile/Process.elm b/src/Data/Textile/Process.elm index e220abb19..5fdb6c592 100644 --- a/src/Data/Textile/Process.elm +++ b/src/Data/Textile/Process.elm @@ -8,6 +8,7 @@ module Data.Textile.Process exposing , encodeUuid , findByAlias , findByUuid + , getDisplayName , getImpact , uuidToString ) @@ -24,19 +25,20 @@ import Json.Encode.Extra as EncodeExtra type alias Process = - { name : String + { alias : Maybe Alias + , correctif : String + , displayName : Maybe String + , elec : Energy -- MJ per kg of material to process + , elec_pppm : Float -- kWh/(pick,m) per kg of material to process + , heat : Energy -- MJ per kg of material to process + , impacts : Impacts , info : String - , unit : String + , name : String , source : String - , correctif : String , stepUsage : String + , unit : String , uuid : Uuid - , impacts : Impacts - , heat : Energy -- MJ per kg of material to process - , elec_pppm : Float -- kWh/(pick,m) per kg of material to process - , elec : Energy -- MJ per kg of material to process , waste : Unit.Ratio -- share of raw material wasted when initially processed - , alias : Maybe Alias } @@ -91,19 +93,26 @@ decodeFromUuid processes = decode : Decoder Impact.Impacts -> Decoder Process decode impactsDecoder = Decode.succeed Process - |> Pipe.required "name" Decode.string + |> Pipe.required "alias" (Decode.maybe decodeAlias) + |> Pipe.required "correctif" Decode.string + |> Pipe.optional "displayName" (Decode.maybe Decode.string) Nothing + |> Pipe.required "elec_MJ" (Decode.map Energy.megajoules Decode.float) + |> Pipe.required "elec_pppm" Decode.float + |> Pipe.required "heat_MJ" (Decode.map Energy.megajoules Decode.float) + |> Pipe.required "impacts" impactsDecoder |> Pipe.required "info" Decode.string - |> Pipe.required "unit" Decode.string + |> Pipe.required "name" Decode.string |> Pipe.required "source" Decode.string - |> Pipe.required "correctif" Decode.string |> Pipe.required "step_usage" Decode.string + |> Pipe.required "unit" Decode.string |> Pipe.required "uuid" decodeUuid - |> Pipe.required "impacts" impactsDecoder - |> Pipe.required "heat_MJ" (Decode.map Energy.megajoules Decode.float) - |> Pipe.required "elec_pppm" Decode.float - |> Pipe.required "elec_MJ" (Decode.map Energy.megajoules Decode.float) |> Pipe.required "waste" (Unit.decodeRatio { percentage = False }) - |> Pipe.required "alias" (Decode.maybe decodeAlias) + + +getDisplayName : Process -> String +getDisplayName process = + process.displayName + |> Maybe.withDefault process.name decodeList : Decoder Impact.Impacts -> Decoder (List Process) @@ -135,6 +144,7 @@ encode : Process -> Encode.Value encode process = Encode.object [ ( "name", Encode.string process.name ) + , ( "displayName", EncodeExtra.maybe Encode.string process.displayName ) , ( "info", Encode.string process.info ) , ( "unit", Encode.string process.unit ) , ( "source", Encode.string process.source ) diff --git a/src/Data/Textile/Product.elm b/src/Data/Textile/Product.elm index 891b02907..d420463cd 100644 --- a/src/Data/Textile/Product.elm +++ b/src/Data/Textile/Product.elm @@ -31,20 +31,20 @@ type alias DyeingOptions = type alias MakingOptions = - { pcrWaste : Split -- PCR product waste ratio - , complexity : MakingComplexity -- How complex is this making + { complexity : MakingComplexity -- How complex is this making + , pcrWaste : Split -- PCR product waste ratio } type alias UseOptions = - { ironingElec : Energy -- Quantitié d'éléctricité mobilisée pour repasser une pièce - , nonIroningProcess : Process -- Procédé composite d'utilisation hors-repassage - , wearsPerCycle : Int -- Nombre de jours porté par cycle d'entretien + { daysOfWear : Duration -- Nombre de jour d'utilisation du vêtement (not used in computations) , defaultNbCycles : Int -- Nombre par défaut de cycles d'entretien (not used in computations) + , ironingElec : Energy -- Quantitié d'éléctricité mobilisée pour repasser une pièce + , nonIroningProcess : Process -- Procédé composite d'utilisation hors-repassage , ratioDryer : Split -- Ratio de séchage électrique (not used in computations) , ratioIroning : Split -- Ratio de repassage (not used in computations) , timeIroning : Duration -- Temps de repassage (not used in computations) - , daysOfWear : Duration -- Nombre de jour d'utilisation du vêtement (not used in computations) + , wearsPerCycle : Int -- Nombre de jours porté par cycle d'entretien } @@ -54,16 +54,16 @@ type alias EndOfLifeOptions = type alias Product = - { id : Id - , name : String - , surfaceMass : Unit.SurfaceMass - , yarnSize : Unit.YarnSize - , fabric : Fabric + { dyeing : DyeingOptions , economics : Economics - , dyeing : DyeingOptions + , endOfLife : EndOfLifeOptions + , fabric : Fabric + , id : Id , making : MakingOptions + , name : String + , surfaceMass : Unit.SurfaceMass , use : UseOptions - , endOfLife : EndOfLifeOptions + , yarnSize : Unit.YarnSize } @@ -99,21 +99,21 @@ decodeDyeingOptions = decodeMakingOptions : Decoder MakingOptions decodeMakingOptions = Decode.succeed MakingOptions - |> Pipe.required "pcrWaste" Split.decodeFloat |> Pipe.required "complexity" MakingComplexity.decode + |> Pipe.required "pcrWaste" Split.decodeFloat decodeUseOptions : List Process -> Decoder UseOptions decodeUseOptions processes = Decode.succeed UseOptions + |> Pipe.required "daysOfWear" (Decode.map Duration.days Decode.float) + |> Pipe.required "defaultNbCycles" Decode.int |> Pipe.required "ironingElecInMJ" (Decode.map Energy.megajoules Decode.float) |> Pipe.required "nonIroningProcessUuid" (Process.decodeFromUuid processes) - |> Pipe.required "wearsPerCycle" Decode.int - |> Pipe.required "defaultNbCycles" Decode.int |> Pipe.required "ratioDryer" Split.decodeFloat |> Pipe.required "ratioIroning" Split.decodeFloat |> Pipe.required "timeIroning" (Decode.map Duration.hours Decode.float) - |> Pipe.required "daysOfWear" (Decode.map Duration.days Decode.float) + |> Pipe.required "wearsPerCycle" Decode.int decodeEndOfLifeOptions : Decoder EndOfLifeOptions @@ -125,16 +125,16 @@ decodeEndOfLifeOptions = decode : List Process -> Decoder Product decode processes = Decode.succeed Product + |> Pipe.required "dyeing" decodeDyeingOptions + |> Pipe.required "economics" Economics.decode + |> Pipe.required "endOfLife" decodeEndOfLifeOptions + |> Pipe.required "fabric" Fabric.decode |> Pipe.required "id" (Decode.map Id Decode.string) + |> Pipe.required "making" decodeMakingOptions |> Pipe.required "name" Decode.string |> Pipe.required "surfaceMass" Unit.decodeSurfaceMass - |> Pipe.required "yarnSize" Unit.decodeYarnSize - |> Pipe.required "fabric" Fabric.decode - |> Pipe.required "economics" Economics.decode - |> Pipe.required "dyeing" decodeDyeingOptions - |> Pipe.required "making" decodeMakingOptions |> Pipe.required "use" (decodeUseOptions processes) - |> Pipe.required "endOfLife" decodeEndOfLifeOptions + |> Pipe.required "yarnSize" Unit.decodeYarnSize decodeList : List Process -> Decoder (List Product) diff --git a/src/Data/Textile/Query.elm b/src/Data/Textile/Query.elm index f7f0d39c3..ba4a9863e 100644 --- a/src/Data/Textile/Query.elm +++ b/src/Data/Textile/Query.elm @@ -45,37 +45,38 @@ import Url.Parser as Parser exposing (Parser) type alias MaterialQuery = - { id : Material.Id + { country : Maybe Country.Code + , id : Material.Id , share : Split , spinning : Maybe Spinning - , country : Maybe Country.Code } type alias Query = - { mass : Mass - , materials : List MaterialQuery - , product : Product.Id - , countrySpinning : Maybe Country.Code - , countryFabric : Maybe Country.Code + { airTransportRatio : Maybe Split + , business : Maybe Economics.Business , countryDyeing : Maybe Country.Code + , countryFabric : Maybe Country.Code , countryMaking : Maybe Country.Code - , airTransportRatio : Maybe Split - , makingWaste : Maybe Split - , makingDeadStock : Maybe Split - , makingComplexity : Maybe MakingComplexity - , yarnSize : Maybe Unit.YarnSize - , surfaceMass : Maybe Unit.SurfaceMass - , fabricProcess : Maybe Fabric + , countrySpinning : Maybe Country.Code , disabledSteps : List Label - , fading : Maybe Bool , dyeingMedium : Maybe DyeingMedium - , printing : Maybe Printing - , business : Maybe Economics.Business + , fabricProcess : Maybe Fabric + , fading : Maybe Bool + , makingComplexity : Maybe MakingComplexity + , makingDeadStock : Maybe Split + , makingWaste : Maybe Split + , mass : Mass + , materials : List MaterialQuery , numberOfReferences : Maybe Int + , physicalDurability : Maybe Unit.PhysicalDurability , price : Maybe Economics.Price + , printing : Maybe Printing + , product : Product.Id + , surfaceMass : Maybe Unit.SurfaceMass , traceability : Maybe Bool , upcycled : Bool + , yarnSize : Maybe Unit.YarnSize } @@ -83,10 +84,10 @@ addMaterial : Material -> Query -> Query addMaterial material query = let materialQuery = - { id = material.id + { country = Nothing + , id = material.id , share = Split.zero , spinning = Nothing - , country = Nothing } in { query @@ -109,56 +110,49 @@ buildApiQuery clientUrl query = decode : Decoder Query decode = Decode.succeed Query - |> Pipe.required "mass" (Decode.map Mass.kilograms Decode.float) - |> Pipe.required "materials" (Decode.list decodeMaterialQuery) - |> Pipe.required "product" (Decode.map Product.Id Decode.string) - |> Pipe.optional "countrySpinning" (Decode.maybe Country.decodeCode) Nothing - |> Pipe.optional "countryFabric" (Decode.maybe Country.decodeCode) Nothing + |> 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 "airTransportRatio" (Decode.maybe Split.decodeFloat) Nothing - |> Pipe.optional "makingWaste" (Decode.maybe Split.decodeFloat) Nothing - |> Pipe.optional "makingDeadStock" (Decode.maybe Split.decodeFloat) Nothing - |> Pipe.optional "makingComplexity" (Decode.maybe MakingComplexity.decode) Nothing - |> Pipe.optional "yarnSize" (Decode.maybe Unit.decodeYarnSize) Nothing - |> Pipe.optional "surfaceMass" (Decode.maybe Unit.decodeSurfaceMass) Nothing - |> Pipe.optional "fabricProcess" (Decode.maybe Fabric.decode) Nothing + |> Pipe.optional "countrySpinning" (Decode.maybe Country.decodeCode) Nothing |> Pipe.optional "disabledSteps" (Decode.list Label.decodeFromCode) [] - |> Pipe.optional "fading" (Decode.maybe Decode.bool) Nothing |> Pipe.optional "dyeingMedium" (Decode.maybe DyeingMedium.decode) Nothing - |> Pipe.optional "printing" (Decode.maybe Printing.decode) Nothing - |> Pipe.optional "business" (Decode.maybe Economics.decodeBusiness) 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 + |> 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 + |> 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 |> Pipe.optional "upcycled" Decode.bool False + |> Pipe.optional "yarnSize" (Decode.maybe Unit.decodeYarnSize) Nothing decodeMaterialQuery : Decoder MaterialQuery decodeMaterialQuery = Decode.succeed MaterialQuery + |> Pipe.optional "country" (Decode.maybe Country.decodeCode) Nothing |> Pipe.required "id" (Decode.map Material.Id Decode.string) |> Pipe.required "share" Split.decodeFloat |> Pipe.optional "spinning" (Decode.maybe Spinning.decode) Nothing - |> Pipe.optional "country" (Decode.maybe Country.decodeCode) Nothing encode : Query -> Encode.Value encode query = - [ ( "mass", query.mass |> Mass.inKilograms |> Encode.float |> Just ) - , ( "materials", query.materials |> Encode.list encodeMaterialQuery |> Just ) - , ( "product", query.product |> Product.idToString |> Encode.string |> Just ) - , ( "countrySpinning", query.countrySpinning |> Maybe.map Country.encodeCode ) - , ( "countryFabric", query.countryFabric |> Maybe.map Country.encodeCode ) + [ ( "airTransportRatio", query.airTransportRatio |> Maybe.map Split.encodeFloat ) + , ( "business", query.business |> Maybe.map Economics.encodeBusiness ) , ( "countryDyeing", query.countryDyeing |> Maybe.map Country.encodeCode ) + , ( "countryFabric", query.countryFabric |> Maybe.map Country.encodeCode ) , ( "countryMaking", query.countryMaking |> Maybe.map Country.encodeCode ) - , ( "airTransportRatio", query.airTransportRatio |> Maybe.map Split.encodeFloat ) - , ( "makingWaste", query.makingWaste |> Maybe.map Split.encodeFloat ) - , ( "makingDeadStock", query.makingDeadStock |> Maybe.map Split.encodeFloat ) - , ( "makingComplexity", query.makingComplexity |> Maybe.map (MakingComplexity.toString >> Encode.string) ) - , ( "yarnSize", query.yarnSize |> Maybe.map Unit.encodeYarnSize ) - , ( "surfaceMass", query.surfaceMass |> Maybe.map Unit.encodeSurfaceMass ) - , ( "fabricProcess", query.fabricProcess |> Maybe.map Fabric.encode ) + , ( "countrySpinning", query.countrySpinning |> Maybe.map Country.encodeCode ) , ( "disabledSteps" , case query.disabledSteps of [] -> @@ -167,14 +161,23 @@ encode query = list -> Encode.list Label.encode list |> Just ) - , ( "fading", query.fading |> Maybe.map Encode.bool ) , ( "dyeingMedium", query.dyeingMedium |> Maybe.map DyeingMedium.encode ) - , ( "printing", query.printing |> Maybe.map Printing.encode ) - , ( "business", query.business |> Maybe.map Economics.encodeBusiness ) + , ( "fabricProcess", query.fabricProcess |> Maybe.map Fabric.encode ) + , ( "fading", query.fading |> Maybe.map Encode.bool ) + , ( "makingComplexity", query.makingComplexity |> Maybe.map (MakingComplexity.toString >> Encode.string) ) + , ( "makingDeadStock", query.makingDeadStock |> Maybe.map Split.encodeFloat ) + , ( "makingWaste", query.makingWaste |> Maybe.map Split.encodeFloat ) + , ( "mass", query.mass |> Mass.inKilograms |> Encode.float |> Just ) + , ( "materials", query.materials |> Encode.list encodeMaterialQuery |> Just ) , ( "numberOfReferences", query.numberOfReferences |> Maybe.map Encode.int ) + , ( "physicalDurability", query.physicalDurability |> Maybe.map Unit.encodePhysicalDurability ) , ( "price", query.price |> Maybe.map Economics.encodePrice ) + , ( "printing", query.printing |> Maybe.map Printing.encode ) + , ( "product", query.product |> Product.idToString |> Encode.string |> Just ) + , ( "surfaceMass", query.surfaceMass |> Maybe.map Unit.encodeSurfaceMass ) , ( "traceability", query.traceability |> Maybe.map Encode.bool ) , ( "upcycled", Encode.bool query.upcycled |> Just ) + , ( "yarnSize", query.yarnSize |> Maybe.map Unit.encodeYarnSize ) ] -- For concision, drop keys where no param is defined |> List.filterMap (\( key, maybeVal ) -> maybeVal |> Maybe.map (\val -> ( key, val ))) @@ -183,10 +186,10 @@ encode query = encodeMaterialQuery : MaterialQuery -> Encode.Value encodeMaterialQuery v = - [ ( "id", Material.encodeId v.id |> Just ) + [ ( "country", v.country |> Maybe.map Country.encodeCode ) + , ( "id", Material.encodeId v.id |> Just ) , ( "share", Split.encodeFloat v.share |> Just ) , ( "spinning", v.spinning |> Maybe.map Spinning.encode ) - , ( "country", v.country |> Maybe.map Country.encodeCode ) ] |> List.filterMap (\( key, maybeVal ) -> maybeVal |> Maybe.map (\val -> ( key, val ))) |> Encode.object @@ -226,15 +229,15 @@ handleUpcycling query = isAdvancedQuery : Query -> Bool isAdvancedQuery query = List.any identity - [ query.materials |> List.any (.spinning >> (/=) Nothing) - , query.makingWaste /= Nothing - , query.makingDeadStock /= Nothing + [ query.dyeingMedium /= Nothing + , query.fabricProcess /= Nothing , query.makingComplexity /= Nothing - , query.yarnSize /= Nothing + , query.makingDeadStock /= Nothing + , query.makingWaste /= Nothing + , query.materials |> List.any (.spinning >> (/=) Nothing) , query.surfaceMass /= Nothing - , query.fabricProcess /= Nothing , not query.upcycled && List.length query.disabledSteps > 0 - , query.dyeingMedium /= Nothing + , query.yarnSize /= Nothing ] @@ -243,15 +246,15 @@ isAdvancedQuery query = regulatory : Query -> Query regulatory query = { query - | materials = query.materials |> List.map (\m -> { m | spinning = Nothing }) - , makingWaste = Nothing - , makingDeadStock = Nothing + | disabledSteps = [] + , dyeingMedium = Nothing + , fabricProcess = Nothing , makingComplexity = Nothing - , yarnSize = Nothing + , makingDeadStock = Nothing + , makingWaste = Nothing + , materials = query.materials |> List.map (\m -> { m | spinning = Nothing }) , surfaceMass = Nothing - , fabricProcess = Nothing - , disabledSteps = [] - , dyeingMedium = Nothing + , yarnSize = Nothing } @@ -272,10 +275,10 @@ updateMaterial oldMaterialId newMaterial = updateMaterialQuery oldMaterialId (\materialQuery -> { materialQuery - | id = newMaterial.id + | country = newMaterial.country + , id = newMaterial.id , share = newMaterial.share , spinning = Nothing - , country = newMaterial.country } ) @@ -312,15 +315,15 @@ updateProduct product query = if product.id /= query.product then -- Product category has changed, reset a bunch of related query params { query - | product = product.id - , makingWaste = Nothing - , makingDeadStock = Nothing - , makingComplexity = Nothing - , yarnSize = Nothing - , surfaceMass = Nothing + | dyeingMedium = Nothing , fabricProcess = Nothing - , dyeingMedium = Nothing + , makingComplexity = Nothing + , makingDeadStock = Nothing + , makingWaste = Nothing , printing = Nothing + , product = product.id + , surfaceMass = Nothing + , yarnSize = Nothing } else @@ -338,27 +341,27 @@ updateStepCountry label code query = Just code in case label of - Label.Spinning -> - { query | countrySpinning = maybeCode } + Label.Ennobling -> + { query | countryDyeing = maybeCode } Label.Fabric -> { query | countryFabric = maybeCode } - Label.Ennobling -> - { query | countryDyeing = maybeCode } - Label.Making -> { query - | countryMaking = maybeCode - , airTransportRatio = + | airTransportRatio = if query.countryMaking /= maybeCode then -- reset custom value as we just switched country Nothing else query.airTransportRatio + , countryMaking = maybeCode } + Label.Spinning -> + { query | countrySpinning = maybeCode } + _ -> query @@ -392,54 +395,55 @@ validateMaterials materials = default : Query default = - { mass = Mass.kilograms 0.17 + { airTransportRatio = Nothing + , business = Nothing + , countryDyeing = Just (Country.Code "CN") + , countryFabric = Just (Country.Code "CN") + , countryMaking = Just (Country.Code "CN") + , countrySpinning = Just (Country.Code "CN") + , disabledSteps = [] + , dyeingMedium = Nothing + , fabricProcess = Nothing + , fading = Nothing + , makingComplexity = Nothing + , makingDeadStock = Nothing + , makingWaste = Nothing + , mass = Mass.kilograms 0.17 , materials = - [ { id = Material.Id "ei-coton" + [ { country = Nothing + , id = Material.Id "ei-coton" , share = Split.full , spinning = Nothing - , country = Nothing } ] - , product = Product.Id "tshirt" - , countrySpinning = Just (Country.Code "CN") - , countryFabric = Just (Country.Code "CN") - , countryDyeing = Just (Country.Code "CN") - , countryMaking = Just (Country.Code "CN") - , airTransportRatio = Nothing - , makingWaste = Nothing - , makingDeadStock = Nothing - , makingComplexity = Nothing - , yarnSize = Nothing - , surfaceMass = Nothing - , fabricProcess = Nothing - , disabledSteps = [] - , fading = Nothing - , dyeingMedium = Nothing - , printing = Nothing - , business = Nothing , numberOfReferences = Nothing + , physicalDurability = Nothing , price = Nothing + , printing = Nothing + , product = Product.Id "tshirt" + , surfaceMass = Nothing , traceability = Nothing , upcycled = False + , yarnSize = Nothing } jupeCotonAsie : Query jupeCotonAsie = { default - | mass = Mass.kilograms 0.3 + | fabricProcess = Just Fabric.Weaving + , mass = Mass.kilograms 0.3 , product = Product.Id "jupe" - , fabricProcess = Just Fabric.Weaving } tShirtCotonFrance : Query tShirtCotonFrance = { default - | countrySpinning = Just (Country.Code "FR") + | countryDyeing = Just (Country.Code "FR") , countryFabric = Just (Country.Code "FR") - , countryDyeing = Just (Country.Code "FR") , countryMaking = Just (Country.Code "FR") + , countrySpinning = Just (Country.Code "FR") } diff --git a/src/Data/Textile/Simulator.elm b/src/Data/Textile/Simulator.elm index f57631dd1..9b435d95a 100644 --- a/src/Data/Textile/Simulator.elm +++ b/src/Data/Textile/Simulator.elm @@ -36,13 +36,13 @@ import Static.Db exposing (Db) type alias Simulator = - { inputs : Inputs - , lifeCycle : LifeCycle + { complementsImpacts : Impact.ComplementsImpacts + , daysOfWear : Duration + , durability : Unit.HolisticDurability , impacts : Impacts - , complementsImpacts : Impact.ComplementsImpacts - , durability : Unit.Durability + , inputs : Inputs + , lifeCycle : LifeCycle , transport : Transport - , daysOfWear : Duration , useNbCycles : Int } @@ -50,13 +50,13 @@ type alias Simulator = encode : Simulator -> Encode.Value encode v = Encode.object - [ ( "inputs", Inputs.encode v.inputs ) - , ( "lifeCycle", LifeCycle.encode v.lifeCycle ) + [ ( "complementsImpacts", Impact.encodeComplementsImpacts v.complementsImpacts ) + , ( "daysOfWear", v.daysOfWear |> Duration.inDays |> round |> Encode.int ) + , ( "durability", v.durability |> Unit.floatDurabilityFromHolistic |> Encode.float ) , ( "impacts", Impact.encode v.impacts ) - , ( "complementsImpacts", Impact.encodeComplementsImpacts v.complementsImpacts ) + , ( "inputs", Inputs.encode v.inputs ) + , ( "lifeCycle", LifeCycle.encode v.lifeCycle ) , ( "transport", Transport.encode v.transport ) - , ( "durability", v.durability |> Unit.durabilityToFloat |> Encode.float ) - , ( "daysOfWear", v.daysOfWear |> Duration.inDays |> round |> Encode.int ) , ( "useNbCycles", Encode.int v.useNbCycles ) ] @@ -70,13 +70,16 @@ init db = inputs |> LifeCycle.init db |> (\lifeCycle -> - { inputs = inputs - , lifeCycle = lifeCycle + { complementsImpacts = Impact.noComplementsImpacts + , daysOfWear = inputs.product.use.daysOfWear + , durability = + { nonPhysical = Unit.standardDurability Unit.NonPhysicalDurability + , physical = inputs.physicalDurability |> Maybe.withDefault (Unit.maxDurability Unit.PhysicalDurability) + } , impacts = Impact.empty - , complementsImpacts = Impact.noComplementsImpacts - , durability = Unit.standardDurability + , inputs = inputs + , lifeCycle = lifeCycle , transport = Transport.default Impact.empty - , daysOfWear = inputs.product.use.daysOfWear , useNbCycles = Product.customDaysOfWear product.use } ) @@ -175,8 +178,8 @@ initializeFinalMass ({ inputs } as simulator) = computeDurability : Simulator -> Simulator computeDurability ({ inputs } as simulator) = let - durability = - Economics.computeDurabilityIndex + nonPhysicalDurability = + Economics.computeNonPhysicalDurabilityIndex { business = inputs.business |> Maybe.withDefault inputs.product.economics.business @@ -191,13 +194,19 @@ computeDurability ({ inputs } as simulator) = inputs.traceability |> Maybe.withDefault inputs.product.economics.traceability } + + newDurability = + { nonPhysical = nonPhysicalDurability + , physical = simulator.durability.physical + } in { simulator - | durability = durability - , daysOfWear = - simulator.daysOfWear |> Quantity.multiplyBy (Unit.durabilityToFloat durability) + | daysOfWear = + simulator.daysOfWear + |> Quantity.multiplyBy (Unit.floatDurabilityFromHolistic newDurability) + , durability = newDurability , useNbCycles = - round (toFloat simulator.useNbCycles * Unit.durabilityToFloat durability) + round (toFloat simulator.useNbCycles * Unit.floatDurabilityFromHolistic newDurability) } @@ -207,20 +216,20 @@ computeEndOfLifeImpacts { textile } simulator = |> updateLifeCycleStep Label.EndOfLife (\({ country } as step) -> let - { kwh, heat, impacts } = + { heat, impacts, kwh } = step.outputMass |> Formula.endOfLifeImpacts step.impacts - { volume = simulator.inputs.product.endOfLife.volume - , passengerCar = textile.wellKnown.passengerCar + { countryElecProcess = country.electricityProcess , endOfLife = textile.wellKnown.endOfLife - , countryElecProcess = country.electricityProcess , heatProcess = country.heatProcess + , passengerCar = textile.wellKnown.passengerCar + , volume = simulator.inputs.product.endOfLife.volume } in { step - | impacts = impacts + | heat = heat + , impacts = impacts , kwh = kwh - , heat = heat } ) @@ -231,13 +240,13 @@ computeUseImpacts ({ inputs, useNbCycles } as simulator) = |> updateLifeCycleStep Label.Use (\({ country } as step) -> let - { kwh, impacts } = + { impacts, kwh } = step.outputMass |> Formula.useImpacts step.impacts - { useNbCycles = useNbCycles + { countryElecProcess = country.electricityProcess , ironingElec = inputs.product.use.ironingElec , nonIroningProcess = inputs.product.use.nonIroningProcess - , countryElecProcess = country.electricityProcess + , useNbCycles = useNbCycles } in { step | impacts = impacts, kwh = kwh } @@ -250,12 +259,11 @@ computeMakingImpacts { textile } ({ inputs } as simulator) = |> updateLifeCycleStep Label.Making (\({ country } as step) -> let - { kwh, heat, impacts } = + { heat, impacts, kwh } = step.outputMass |> Formula.makingImpacts step.impacts - { makingComplexity = - inputs.fabricProcess - |> Fabric.getMakingComplexity inputs.product.making.complexity inputs.makingComplexity + { countryElecProcess = country.electricityProcess + , countryHeatProcess = country.heatProcess , fadingProcess = -- Note: in the future, we may have distinct fading processes per countries if inputs.fading == Just True then @@ -263,11 +271,12 @@ computeMakingImpacts { textile } ({ inputs } as simulator) = else Nothing - , countryElecProcess = country.electricityProcess - , countryHeatProcess = country.heatProcess + , makingComplexity = + inputs.fabricProcess + |> Fabric.getMakingComplexity inputs.product.making.complexity inputs.makingComplexity } in - { step | impacts = impacts, kwh = kwh, heat = heat } + { step | heat = heat, impacts = impacts, kwh = kwh } ) @@ -293,20 +302,20 @@ computeDyeingImpacts { textile } ({ inputs } as simulator) = |> List.map (\{ material, share } -> Formula.materialDyeingToxicityImpacts step.impacts - { dyeingToxicityProcess = + { aquaticPollutionScenario = step.country.aquaticPollutionScenario + , dyeingToxicityProcess = if Origin.isSynthetic material.origin then textile.wellKnown.dyeingSynthetic else textile.wellKnown.dyeingCellulosic - , aquaticPollutionScenario = step.country.aquaticPollutionScenario } step.outputMass share ) |> Impact.sumImpacts - { heat, kwh, impacts } = + { heat, impacts, kwh } = step.outputMass |> Formula.dyeingImpacts step.impacts dyeingProcess @@ -315,8 +324,8 @@ computeDyeingImpacts { textile } ({ inputs } as simulator) = in { step | heat = step.heat |> Quantity.plus heat - , kwh = step.kwh |> Quantity.plus kwh , impacts = Impact.sumImpacts [ step.impacts, impacts, dyeingToxicity ] + , kwh = step.kwh |> Quantity.plus kwh } ) @@ -332,29 +341,29 @@ computePrintingImpacts { textile } ({ inputs } as simulator) = { printingProcess, printingToxicityProcess } = WellKnown.getPrintingProcess kind textile.wellKnown - { heat, kwh, impacts } = + { heat, impacts, kwh } = step.outputMass |> Formula.printingImpacts step.impacts - { printingProcess = printingProcess + { elecProcess = country.electricityProcess , heatProcess = WellKnown.getEnnoblingHeatProcess textile.wellKnown country - , elecProcess = country.electricityProcess - , surfaceMass = Maybe.withDefault inputs.product.surfaceMass inputs.surfaceMass + , printingProcess = printingProcess , ratio = ratio + , surfaceMass = Maybe.withDefault inputs.product.surfaceMass inputs.surfaceMass } printingToxicity = step.outputMass |> Formula.materialPrintingToxicityImpacts step.impacts - { printingToxicityProcess = printingToxicityProcess - , aquaticPollutionScenario = step.country.aquaticPollutionScenario + { aquaticPollutionScenario = step.country.aquaticPollutionScenario + , printingToxicityProcess = printingToxicityProcess } ratio in { step | heat = step.heat |> Quantity.plus heat - , kwh = step.kwh |> Quantity.plus kwh , impacts = Impact.sumImpacts [ step.impacts, impacts, printingToxicity ] + , kwh = step.kwh |> Quantity.plus kwh } Nothing -> @@ -368,18 +377,18 @@ computeFinishingImpacts { textile } simulator = |> updateLifeCycleStep Label.Ennobling (\({ country } as step) -> let - { heat, kwh, impacts } = + { heat, impacts, kwh } = step.outputMass |> Formula.finishingImpacts step.impacts - { finishingProcess = textile.wellKnown.finishing + { elecProcess = country.electricityProcess + , finishingProcess = textile.wellKnown.finishing , heatProcess = WellKnown.getEnnoblingHeatProcess textile.wellKnown country - , elecProcess = country.electricityProcess } in { step | heat = step.heat |> Quantity.plus heat - , kwh = step.kwh |> Quantity.plus kwh , impacts = Impact.sumImpacts [ step.impacts, impacts ] + , kwh = step.kwh |> Quantity.plus kwh } ) @@ -393,8 +402,8 @@ computeBleachingImpacts { textile } simulator = impacts = step.outputMass |> Formula.bleachingImpacts step.impacts - { bleachingProcess = textile.wellKnown.bleaching - , aquaticPollutionScenario = step.country.aquaticPollutionScenario + { aquaticPollutionScenario = step.country.aquaticPollutionScenario + , bleachingProcess = textile.wellKnown.bleaching } in { step @@ -406,20 +415,20 @@ computeBleachingImpacts { textile } simulator = stepMaterialImpacts : Db -> Material -> Step -> Impacts stepMaterialImpacts { textile } material step = case Material.getRecyclingData material textile.materials of - -- Non-recycled Material - Nothing -> - step.outputMass - |> Formula.pureMaterialImpacts step.impacts material.materialProcess - -- Recycled material: apply CFF Just ( sourceMaterial, cffData ) -> step.outputMass |> Formula.recycledMaterialImpacts step.impacts - { recycledProcess = material.materialProcess + { cffData = cffData , nonRecycledProcess = sourceMaterial.materialProcess - , cffData = cffData + , recycledProcess = material.materialProcess } + -- Non-recycled Material + Nothing -> + step.outputMass + |> Formula.pureMaterialImpacts step.impacts material.materialProcess + computeMaterialImpacts : Db -> Simulator -> Simulator computeMaterialImpacts db ({ inputs } as simulator) = @@ -440,7 +449,7 @@ computeMaterialImpacts db ({ inputs } as simulator) = ) -stepSpinningImpacts : Material -> Maybe Spinning -> Product -> Step -> { impacts : Impacts, kwh : Energy } +stepSpinningImpacts : Material -> Maybe Spinning -> Product -> Step -> { heat : Energy, impacts : Impacts, kwh : Energy } stepSpinningImpacts material maybeSpinning product step = let yarnSize = @@ -457,8 +466,8 @@ stepSpinningImpacts material maybeSpinning product step = |> Energy.kilowattHours in Formula.spinningImpacts step.impacts - { spinningKwh = kwh - , countryElecProcess = step.country.electricityProcess + { countryElecProcess = step.country.electricityProcess + , spinningKwh = kwh } @@ -468,26 +477,26 @@ computeSpinningImpacts ({ inputs } as simulator) = |> updateLifeCycleStep Label.Spinning (\step -> { step - | kwh = + | impacts = inputs.materials |> List.map (\{ material, share, spinning } -> step |> stepSpinningImpacts material spinning inputs.product - |> .kwh - |> Quantity.multiplyBy (Split.toFloat share) + |> .impacts + |> Impact.mapImpacts (\_ -> Quantity.multiplyBy (Split.toFloat share)) ) - |> List.foldl Quantity.plus Quantity.zero - , impacts = + |> Impact.sumImpacts + , kwh = inputs.materials |> List.map (\{ material, share, spinning } -> step |> stepSpinningImpacts material spinning inputs.product - |> .impacts - |> Impact.mapImpacts (\_ -> Quantity.multiplyBy (Split.toFloat share)) + |> .kwh + |> Quantity.multiplyBy (Split.toFloat share) ) - |> Impact.sumImpacts + |> List.foldl Quantity.plus Quantity.zero } ) @@ -508,17 +517,17 @@ computeFabricImpacts { textile } ({ inputs, lifeCycle } as simulator) = |> Maybe.withDefault inputs.product.fabric |> Fabric.getProcess textile.wellKnown - { kwh, threadDensity, picking, impacts } = + { impacts, kwh, picking, threadDensity } = if inputs.fabricProcess |> Maybe.withDefault inputs.product.fabric |> Fabric.isKnitted then - Formula.knittingImpacts step.impacts - { elec = process.elec - , countryElecProcess = country.electricityProcess - } - step.outputMass + step.outputMass + |> Formula.knittingImpacts step.impacts + { countryElecProcess = country.electricityProcess + , elec = process.elec + } else let @@ -534,14 +543,19 @@ computeFabricImpacts { textile } ({ inputs, lifeCycle } as simulator) = , yarnSize = inputs.yarnSize |> Maybe.withDefault inputs.product.yarnSize } in - { step | impacts = impacts, threadDensity = threadDensity, kwh = kwh, picking = picking } + { step + | impacts = impacts + , kwh = kwh + , picking = picking + , threadDensity = threadDensity + } ) computeMakingStepWaste : Simulator -> Simulator computeMakingStepWaste ({ inputs } as simulator) = let - { product, makingWaste, fabricProcess } = + { fabricProcess, makingWaste, product } = inputs { mass, waste } = @@ -559,7 +573,7 @@ computeMakingStepWaste ({ inputs } as simulator) = computeMakingStepDeadStock : Simulator -> Simulator computeMakingStepDeadStock ({ inputs, lifeCycle } as simulator) = let - { mass, deadstock } = + { deadstock, mass } = lifeCycle |> LifeCycle.getStepProp Label.Making .inputMass Quantity.zero |> Formula.makingDeadStock (Maybe.withDefault Env.defaultDeadStock inputs.makingDeadStock) @@ -645,8 +659,8 @@ computeSpinningStepWaste ({ inputs, lifeCycle } as simulator) = |> Split.divideBy (Mass.inKilograms outputMaterialMass) |> Mass.kilograms in - { waste = Quantity.difference inputMaterialMass outputMaterialMass - , mass = inputMaterialMass + { mass = inputMaterialMass + , waste = Quantity.difference inputMaterialMass outputMaterialMass } ) |> List.foldl @@ -683,14 +697,14 @@ computeFinalImpacts ({ durability, lifeCycle } as simulator) = lifeCycle |> Array.filter .enabled |> LifeCycle.sumComplementsImpacts - |> Impact.divideComplementsImpactsBy (Unit.durabilityToFloat durability) + |> Impact.divideComplementsImpactsBy (Unit.floatDurabilityFromHolistic durability) in { simulator | complementsImpacts = complementsImpacts , impacts = lifeCycle |> LifeCycle.computeFinalImpacts - |> Impact.divideBy (Unit.durabilityToFloat durability) + |> Impact.divideBy (Unit.floatDurabilityFromHolistic durability) |> Impact.impactsWithComplements complementsImpacts } @@ -702,7 +716,7 @@ getTotalImpactsWithoutComplements { durability, lifeCycle } = |> Array.map Step.getTotalImpactsWithoutComplements |> Array.toList |> Impact.sumImpacts - |> Impact.divideBy (Unit.durabilityToFloat durability) + |> Impact.divideBy (Unit.floatDurabilityFromHolistic durability) updateLifeCycle : (LifeCycle -> LifeCycle) -> Simulator -> Simulator @@ -739,17 +753,23 @@ toStepsImpacts trigram simulator = Maybe.map (Quantity.minus (complementImpact - |> Quantity.multiplyBy (Unit.durabilityToFloat simulator.durability) + |> Quantity.multiplyBy (Unit.floatDurabilityFromHolistic simulator.durability) ) ) else identity in - { materials = + { distribution = Nothing + , endOfLife = + getImpacts Label.EndOfLife + |> getImpact + |> applyComplement simulator.complementsImpacts.outOfEuropeEOL + , materials = getImpacts Label.Material |> getImpact |> applyComplement simulator.complementsImpacts.microfibers + , packaging = Nothing , transform = [ getImpacts Label.Spinning , getImpacts Label.Fabric @@ -758,13 +778,7 @@ toStepsImpacts trigram simulator = ] |> Impact.sumImpacts |> getImpact - , packaging = Nothing , transports = getImpact simulator.transport.impacts - , distribution = Nothing , usage = getImpacts Label.Use |> getImpact - , endOfLife = - getImpacts Label.EndOfLife - |> getImpact - |> applyComplement simulator.complementsImpacts.outOfEuropeEOL } - |> Impact.divideStepsImpactsBy (Unit.durabilityToFloat simulator.durability) + |> Impact.divideStepsImpactsBy (Unit.floatDurabilityFromHolistic simulator.durability) diff --git a/src/Data/Textile/Step.elm b/src/Data/Textile/Step.elm index 8570bf453..cc58a0bb5 100644 --- a/src/Data/Textile/Step.elm +++ b/src/Data/Textile/Step.elm @@ -45,106 +45,106 @@ import Views.Format as Format type alias Step = - { label : Label - , enabled : Bool + { airTransportRatio : Split + , complementsImpacts : Impact.ComplementsImpacts , country : Country - , editable : Bool - , inputMass : Mass - , outputMass : Mass - , waste : Mass , deadstock : Mass - , transport : Transport - , impacts : Impacts - , complementsImpacts : Impact.ComplementsImpacts + , durability : Unit.NonPhysicalDurability + , dyeingMedium : Maybe DyeingMedium + , editable : Bool + , enabled : Bool , heat : Energy + , impacts : Impacts + , inputMass : Mass , kwh : Energy - , processInfo : ProcessInfo - , airTransportRatio : Split -- FIXME: why not Maybe? - , durability : Unit.Durability + , label : Label , makingComplexity : Maybe MakingComplexity - , makingWaste : Maybe Split , makingDeadStock : Maybe Split + , makingWaste : Maybe Split + , outputMass : Mass , picking : Maybe Unit.PickPerMeter + , printing : Maybe Printing + , processInfo : ProcessInfo + , surfaceMass : Maybe Unit.SurfaceMass , threadDensity : Maybe Unit.ThreadDensity + , transport : Transport + , waste : Mass , yarnSize : Maybe Unit.YarnSize - , surfaceMass : Maybe Unit.SurfaceMass - , dyeingMedium : Maybe DyeingMedium - , printing : Maybe Printing } type alias ProcessInfo = - { countryElec : Maybe String - , countryHeat : Maybe String + { airTransport : Maybe String , airTransportRatio : Maybe String - , airTransport : Maybe String - , seaTransport : Maybe String - , roadTransport : Maybe String - , useIroning : Maybe String - , useNonIroning : Maybe String - , passengerCar : Maybe String + , countryElec : Maybe String + , countryHeat : Maybe String + , distribution : Maybe String + , dyeing : Maybe String , endOfLife : Maybe String , fabric : Maybe String - , dyeing : Maybe String - , making : Maybe String - , distribution : Maybe String , fading : Maybe String + , making : Maybe String + , passengerCar : Maybe String , printing : Maybe String + , roadTransport : Maybe String + , seaTransport : Maybe String + , useIroning : Maybe String + , useNonIroning : Maybe String } -create : { label : Label, editable : Bool, country : Country, enabled : Bool } -> Step -create { label, editable, country, enabled } = +create : { country : Country, editable : Bool, enabled : Bool, label : Label } -> Step +create { country, editable, enabled, label } = let defaultImpacts = Impact.empty in - { label = label - , enabled = enabled + { airTransportRatio = Split.zero -- Note: this depends on next step country, so we can't set an accurate default value initially + , complementsImpacts = Impact.noComplementsImpacts , country = country - , editable = editable - , inputMass = Quantity.zero - , outputMass = Quantity.zero - , waste = Quantity.zero , deadstock = Quantity.zero - , transport = Transport.default defaultImpacts - , impacts = defaultImpacts - , complementsImpacts = Impact.noComplementsImpacts + , durability = Unit.standardDurability Unit.NonPhysicalDurability + , dyeingMedium = Nothing + , editable = editable + , enabled = enabled , heat = Quantity.zero + , impacts = defaultImpacts + , inputMass = Quantity.zero , kwh = Quantity.zero - , processInfo = defaultProcessInfo - , airTransportRatio = Split.zero -- Note: this depends on next step country, so we can't set an accurate default value initially - , durability = Unit.standardDurability + , label = label , makingComplexity = Nothing - , makingWaste = Nothing , makingDeadStock = Nothing + , makingWaste = Nothing + , outputMass = Quantity.zero , picking = Nothing + , printing = Nothing + , processInfo = defaultProcessInfo + , surfaceMass = Nothing , threadDensity = Nothing + , transport = Transport.default defaultImpacts + , waste = Quantity.zero , yarnSize = Nothing - , surfaceMass = Nothing - , dyeingMedium = Nothing - , printing = Nothing } defaultProcessInfo : ProcessInfo defaultProcessInfo = - { countryElec = Nothing - , countryHeat = Nothing + { airTransport = Nothing , airTransportRatio = Nothing - , airTransport = Nothing - , seaTransport = Nothing - , roadTransport = Nothing - , useIroning = Nothing - , useNonIroning = Nothing - , passengerCar = Nothing + , countryElec = Nothing + , countryHeat = Nothing + , distribution = Nothing + , dyeing = Nothing , endOfLife = Nothing , fabric = Nothing - , dyeing = Nothing - , making = Nothing - , distribution = Nothing , fading = Nothing + , making = Nothing + , passengerCar = Nothing , printing = Nothing + , roadTransport = Nothing + , seaTransport = Nothing + , useIroning = Nothing + , useNonIroning = Nothing } @@ -187,21 +187,17 @@ computeTransports db inputs next ({ processInfo } as current) = { current | processInfo = { processInfo - | roadTransport = Just db.textile.wellKnown.roadTransport.name + | airTransport = Just db.textile.wellKnown.airTransport.name + , roadTransport = Just db.textile.wellKnown.roadTransport.name , seaTransport = Just db.textile.wellKnown.seaTransport.name - , airTransport = Just db.textile.wellKnown.airTransport.name } , transport = transport } computeTransportImpacts : Impacts -> WellKnown -> Process -> Mass -> Transport -> Transport -computeTransportImpacts impacts { seaTransport, airTransport } roadProcess mass { road, sea, air } = - { road = road - , roadCooled = Quantity.zero - , sea = sea - , seaCooled = Quantity.zero - , air = air +computeTransportImpacts impacts { airTransport, seaTransport } roadProcess mass { air, road, sea } = + { air = air , impacts = impacts |> Impact.mapImpacts @@ -215,6 +211,10 @@ computeTransportImpacts impacts { seaTransport, airTransport } roadProcess mass in Quantity.sum [ roadImpact, seaImpact, airImpact ] ) + , road = road + , roadCooled = Quantity.zero + , sea = sea + , seaCooled = Quantity.zero } @@ -227,6 +227,14 @@ computeTransportSummary step transport = ) in case step.label of + Label.Distribution -> + -- Product Distribution leverages no transports + noTransports + + Label.EndOfLife -> + -- End of life leverages no transports + noTransports + Label.Making -> -- Air transport only applies between the Making and the Distribution steps transport @@ -236,18 +244,10 @@ computeTransportSummary step transport = -- Also ensure we don't add unnecessary air transport |> Transport.add { defaultInland | air = Quantity.zero } - Label.Distribution -> - -- Product Distribution leverages no transports - noTransports - Label.Use -> -- Product Use leverages no transports noTransports - Label.EndOfLife -> - -- End of life leverages no transports - noTransports - _ -> -- All other steps don't use air transport, force a 0 split transport @@ -281,40 +281,28 @@ getTransportedMass inputs { label, outputMass } = updateFromInputs : Textile.Db -> Inputs -> Step -> Step updateFromInputs { wellKnown } inputs ({ label, country, complementsImpacts } as step) = let - { airTransportRatio, makingComplexity, makingWaste, makingDeadStock, yarnSize, surfaceMass, dyeingMedium, printing } = + { airTransportRatio, dyeingMedium, makingComplexity, makingDeadStock, makingWaste, printing, surfaceMass, yarnSize } = inputs in case label of - Label.Material -> + Label.Distribution -> { step - | complementsImpacts = - { complementsImpacts - -- Note: no other steps than the Material one generate microfibers pollution - | microfibers = Inputs.getTotalMicrofibersComplement inputs - } + | processInfo = + { defaultProcessInfo | distribution = Just wellKnown.distribution.name } } - Label.Spinning -> + Label.EndOfLife -> { step - | yarnSize = yarnSize - , processInfo = - { defaultProcessInfo - | countryElec = Just country.electricityProcess.name + | complementsImpacts = + { complementsImpacts + | outOfEuropeEOL = Inputs.getOutOfEuropeEOLComplement inputs } - } - - Label.Fabric -> - { step - | yarnSize = yarnSize - , surfaceMass = surfaceMass , processInfo = { defaultProcessInfo | countryElec = Just country.electricityProcess.name - , fabric = - inputs.product.fabric - |> Fabric.getProcess wellKnown - |> .name - |> Just + , countryHeat = Just country.heatProcess.name + , endOfLife = Just wellKnown.endOfLife.name + , passengerCar = Just "Transport en voiture vers point de collecte (1km)" } } @@ -324,8 +312,8 @@ updateFromInputs { wellKnown } inputs ({ label, country, complementsImpacts } as , printing = printing , processInfo = { defaultProcessInfo - | countryHeat = Just country.heatProcess.name - , countryElec = Just country.electricityProcess.name + | countryElec = Just country.electricityProcess.name + , countryHeat = Just country.heatProcess.name , dyeing = wellKnown |> WellKnown.getDyeingProcess @@ -343,28 +331,52 @@ updateFromInputs { wellKnown } inputs ({ label, country, complementsImpacts } as } } + Label.Fabric -> + { step + | processInfo = + { defaultProcessInfo + | countryElec = Just country.electricityProcess.name + , fabric = + inputs.product.fabric + |> Fabric.getProcess wellKnown + |> .name + |> Just + } + , surfaceMass = surfaceMass + , yarnSize = yarnSize + } + Label.Making -> { step | airTransportRatio = airTransportRatio |> Maybe.withDefault country.airTransportRatio - , makingWaste = makingWaste - , makingDeadStock = makingDeadStock , makingComplexity = makingComplexity + , makingDeadStock = makingDeadStock + , makingWaste = makingWaste , processInfo = { defaultProcessInfo - | countryElec = Just country.electricityProcess.name - , fading = Just wellKnown.fading.name - , airTransportRatio = + | airTransportRatio = country.airTransportRatio |> airTransportRatioToString |> Just + , countryElec = Just country.electricityProcess.name + , fading = Just wellKnown.fading.name } } - Label.Distribution -> + Label.Material -> { step - | processInfo = - { defaultProcessInfo | distribution = Just wellKnown.distribution.name } + | complementsImpacts = + { complementsImpacts + -- Note: no other steps than the Material one generate microfibers pollution + | microfibers = Inputs.getTotalMicrofibersComplement inputs + } + } + + Label.Spinning -> + { step + | processInfo = { defaultProcessInfo | countryElec = Just country.electricityProcess.name } + , yarnSize = yarnSize } Label.Use -> @@ -383,21 +395,6 @@ updateFromInputs { wellKnown } inputs ({ label, country, complementsImpacts } as } } - Label.EndOfLife -> - { step - | complementsImpacts = - { complementsImpacts - | outOfEuropeEOL = Inputs.getOutOfEuropeEOLComplement inputs - } - , processInfo = - { defaultProcessInfo - | passengerCar = Just "Transport en voiture vers point de collecte (1km)" - , countryElec = Just country.electricityProcess.name - , countryHeat = Just country.heatProcess.name - , endOfLife = Just wellKnown.endOfLife.name - } - } - initMass : Mass -> Step -> Step initMass mass step = @@ -410,9 +407,9 @@ initMass mass step = updateWasteAndMasses : Mass -> Mass -> Step -> Step updateWasteAndMasses waste mass step = { step - | waste = waste - , inputMass = mass + | inputMass = mass , outputMass = Quantity.difference mass waste + , waste = waste } @@ -426,7 +423,7 @@ updateDeadStock deadstock mass step = airTransportDisabled : Step -> Bool -airTransportDisabled { enabled, label, country } = +airTransportDisabled { country, enabled, label } = not enabled || -- Note: disallow air transport from France to France at the Making step (label == Label.Making && country.code == Country.codeFromString "FR") @@ -478,32 +475,28 @@ yarnSizeToDtexString yarnSize = encode : Step -> Encode.Value encode v = Encode.object - [ ( "label", Encode.string (Label.toString v.label) ) - , ( "enabled", Encode.bool v.enabled ) + [ ( "airTransportRatio", Split.encodeFloat v.airTransportRatio ) + , ( "complementsImpacts", Impact.encodeComplementsImpacts v.complementsImpacts ) , ( "country", Country.encode v.country ) - , ( "editable", Encode.bool v.editable ) - , ( "inputMass", Encode.float (Mass.inKilograms v.inputMass) ) - , ( "outputMass", Encode.float (Mass.inKilograms v.outputMass) ) - , ( "waste", Encode.float (Mass.inKilograms v.waste) ) , ( "deadstock", Encode.float (Mass.inKilograms v.deadstock) ) - , ( "transport", Transport.encode v.transport ) - , ( "impacts" - , v.impacts - |> Impact.applyComplements (Impact.getTotalComplementsImpacts v.complementsImpacts) - |> Impact.encode - ) - , ( "complementsImpacts", Impact.encodeComplementsImpacts v.complementsImpacts ) - , ( "heat_MJ", Encode.float (Energy.inMegajoules v.heat) ) + , ( "durability", Unit.encodeNonPhysicalDurability v.durability ) + , ( "editable", Encode.bool v.editable ) , ( "elec_kWh", Encode.float (Energy.inKilowattHours v.kwh) ) - , ( "processInfo", encodeProcessInfo v.processInfo ) - , ( "airTransportRatio", Split.encodeFloat v.airTransportRatio ) - , ( "durability", Unit.encodeDurability v.durability ) - , ( "makingWaste", v.makingWaste |> Maybe.map Split.encodeFloat |> Maybe.withDefault Encode.null ) + , ( "enabled", Encode.bool v.enabled ) + , ( "heat_MJ", Encode.float (Energy.inMegajoules v.heat) ) + , ( "impacts", v.impacts |> Impact.applyComplements (Impact.getTotalComplementsImpacts v.complementsImpacts) |> Impact.encode ) + , ( "inputMass", Encode.float (Mass.inKilograms v.inputMass) ) + , ( "label", Encode.string (Label.toString v.label) ) , ( "makingDeadStock", v.makingDeadStock |> Maybe.map Split.encodeFloat |> Maybe.withDefault Encode.null ) + , ( "makingWaste", v.makingWaste |> Maybe.map Split.encodeFloat |> Maybe.withDefault Encode.null ) + , ( "outputMass", Encode.float (Mass.inKilograms v.outputMass) ) , ( "picking", v.picking |> Maybe.map Unit.encodePickPerMeter |> Maybe.withDefault Encode.null ) + , ( "processInfo", encodeProcessInfo v.processInfo ) + , ( "surfaceMass", v.surfaceMass |> Maybe.map Unit.encodeSurfaceMass |> Maybe.withDefault Encode.null ) , ( "threadDensity", v.threadDensity |> Maybe.map Unit.encodeThreadDensity |> Maybe.withDefault Encode.null ) + , ( "transport", Transport.encode v.transport ) + , ( "waste", Encode.float (Mass.inKilograms v.waste) ) , ( "yarnSize", v.yarnSize |> Maybe.map Unit.encodeYarnSize |> Maybe.withDefault Encode.null ) - , ( "surfaceMass", v.surfaceMass |> Maybe.map Unit.encodeSurfaceMass |> Maybe.withDefault Encode.null ) ] @@ -514,20 +507,20 @@ encodeProcessInfo v = Maybe.map Encode.string >> Maybe.withDefault Encode.null in Encode.object - [ ( "countryElec", encodeMaybeString v.countryElec ) - , ( "countryHeat", encodeMaybeString v.countryHeat ) + [ ( "airTransport", encodeMaybeString v.airTransport ) , ( "airTransportRatio", encodeMaybeString v.airTransportRatio ) - , ( "airTransport", encodeMaybeString v.airTransport ) - , ( "seaTransport", encodeMaybeString v.seaTransport ) - , ( "roadTransport", encodeMaybeString v.roadTransport ) - , ( "useIroning", encodeMaybeString v.useIroning ) - , ( "useNonIroning", encodeMaybeString v.useNonIroning ) - , ( "passengerCar", encodeMaybeString v.passengerCar ) + , ( "countryElec", encodeMaybeString v.countryElec ) + , ( "countryHeat", encodeMaybeString v.countryHeat ) + , ( "distribution", encodeMaybeString v.distribution ) , ( "endOfLife", encodeMaybeString v.endOfLife ) , ( "fabric", encodeMaybeString v.fabric ) - , ( "making", encodeMaybeString v.making ) - , ( "distribution", encodeMaybeString v.distribution ) , ( "fading", encodeMaybeString v.fading ) + , ( "making", encodeMaybeString v.making ) + , ( "passengerCar", encodeMaybeString v.passengerCar ) + , ( "roadTransport", encodeMaybeString v.roadTransport ) + , ( "seaTransport", encodeMaybeString v.seaTransport ) + , ( "useIroning", encodeMaybeString v.useIroning ) + , ( "useNonIroning", encodeMaybeString v.useNonIroning ) ] diff --git a/src/Data/Textile/Step/Label.elm b/src/Data/Textile/Step/Label.elm index 5a9ba2edd..51e1333a3 100644 --- a/src/Data/Textile/Step/Label.elm +++ b/src/Data/Textile/Step/Label.elm @@ -20,14 +20,14 @@ import Json.Encode as Encode type Label - = Material -- Matière - | Spinning -- Filature - | Fabric -- Tissage ou Tricotage + = Distribution -- Distribution + | EndOfLife -- Fin de vie | Ennobling -- Ennoblissement + | Fabric -- Tissage ou Tricotage | Making -- Confection - | Distribution -- Distribution + | Material -- Matière + | Spinning -- Filature | Use -- Utilisation - | EndOfLife -- Fin de vie all : List Label @@ -55,10 +55,13 @@ upcyclables = toColor : Label -> String toColor label = case label of - Material -> - Impact.stepsColors.materials + Distribution -> + Impact.stepsColors.distribution - Spinning -> + EndOfLife -> + Impact.stepsColors.endOfLife + + Ennobling -> Impact.stepsColors.transform Fabric -> @@ -67,29 +70,27 @@ toColor label = Making -> Impact.stepsColors.transform - Ennobling -> - Impact.stepsColors.transform + Material -> + Impact.stepsColors.materials - Distribution -> - Impact.stepsColors.distribution + Spinning -> + Impact.stepsColors.transform Use -> Impact.stepsColors.usage - EndOfLife -> - Impact.stepsColors.endOfLife - toId : Label -> String toId label = case label of - Material -> - "materials-step" + Distribution -> + "distribution-step" - Spinning -> - -- We only want a single "transform-step" id, as it's used for the Html `id` attribute - -- and they are meant to be unique throughout the page. - "transform-step" + EndOfLife -> + "end-of-life-step" + + Ennobling -> + "transform-step-ennobling" Fabric -> "transform-step-fabric" @@ -97,24 +98,23 @@ toId label = Making -> "transform-step-making" - Ennobling -> - "transform-step-ennobling" + Material -> + "materials-step" - Distribution -> - "distribution-step" + Spinning -> + -- We only want a single "transform-step" id, as it's used for the Html `id` attribute + -- and they are meant to be unique throughout the page. + "transform-step" Use -> "usage-step" - EndOfLife -> - "end-of-life-step" - toName : Label -> String toName label = case label of - Spinning -> - "Transformation\u{00A0}- Filature" + Ennobling -> + "Transformation\u{00A0}- Ennoblissement" Fabric -> "Transformation\u{00A0}- Tissage / Tricotage" @@ -122,8 +122,8 @@ toName label = Making -> "Transformation\u{00A0}- Confection" - Ennobling -> - "Transformation\u{00A0}- Ennoblissement" + Spinning -> + "Transformation\u{00A0}- Filature" _ -> toString label @@ -132,11 +132,14 @@ toName label = toString : Label -> String toString label = case label of - Material -> - "Matières premières" + Distribution -> + "Distribution" - Spinning -> - "Filature" + EndOfLife -> + "Fin de vie" + + Ennobling -> + "Ennoblissement" Fabric -> "Tissage & Tricotage" @@ -144,27 +147,27 @@ toString label = Making -> "Confection" - Ennobling -> - "Ennoblissement" + Material -> + "Matières premières" - Distribution -> - "Distribution" + Spinning -> + "Filature" Use -> "Utilisation" - EndOfLife -> - "Fin de vie" - fromCodeString : String -> Result String Label fromCodeString code = case code of - "material" -> - Ok Material + "distribution" -> + Ok Distribution - "spinning" -> - Ok Spinning + "ennobling" -> + Ok Ennobling + + "eol" -> + Ok EndOfLife "fabric" -> Ok Fabric @@ -172,18 +175,15 @@ fromCodeString code = "making" -> Ok Making - "ennobling" -> - Ok Ennobling + "material" -> + Ok Material - "distribution" -> - Ok Distribution + "spinning" -> + Ok Spinning "use" -> Ok Use - "eol" -> - Ok EndOfLife - _ -> Err ("Code étape inconnu: " ++ code) @@ -191,11 +191,14 @@ fromCodeString code = toCodeString : Label -> String toCodeString label = case label of - Material -> - "material" + Distribution -> + "distribution" - Spinning -> - "spinning" + EndOfLife -> + "eol" + + Ennobling -> + "ennobling" Fabric -> "fabric" @@ -203,46 +206,43 @@ toCodeString label = Making -> "making" - Ennobling -> - "ennobling" + Material -> + "material" - Distribution -> - "distribution" + Spinning -> + "spinning" Use -> "use" - EndOfLife -> - "eol" - toGitbookPath : Label -> Gitbook.Path toGitbookPath label = case label of - Material -> - Gitbook.TextileMaterial - - Spinning -> - Gitbook.TextileSpinning + Distribution -> + Gitbook.TextileDistribution - Fabric -> - Gitbook.TextileFabric + EndOfLife -> + Gitbook.TextileEndOfLife Ennobling -> Gitbook.TextileEnnobling + Fabric -> + Gitbook.TextileFabric + Making -> Gitbook.TextileMaking - Distribution -> - Gitbook.TextileDistribution + Material -> + Gitbook.TextileMaterial + + Spinning -> + Gitbook.TextileSpinning Use -> Gitbook.TextileUse - EndOfLife -> - Gitbook.TextileEndOfLife - decodeFromCode : Decoder Label decodeFromCode = diff --git a/src/Data/Textile/WellKnown.elm b/src/Data/Textile/WellKnown.elm index 5fe5f5c47..f8fd5255c 100644 --- a/src/Data/Textile/WellKnown.elm +++ b/src/Data/Textile/WellKnown.elm @@ -17,30 +17,30 @@ import Result.Extra as RE type alias WellKnown = { airTransport : Process , bleaching : Process - , seaTransport : Process - , roadTransport : Process - , trainTransport : Process , distribution : Process - , dyeingYarn : Process - , dyeingFabric : Process , dyeingArticle : Process - , dyeingSynthetic : Process , dyeingCellulosic : Process - , knittingMix : Process + , dyeingFabric : Process + , dyeingSynthetic : Process + , dyeingYarn : Process + , endOfLife : Process + , fading : Process + , finishing : Process + , heatEurope : Process + , heatRoW : Process + , knittingCircular : Process , knittingFullyFashioned : Process + , knittingMix : Process , knittingSeamless : Process - , knittingCircular : Process , knittingStraight : Process + , passengerCar : Process + , printingDyes : Process + , printingPaste : Process , printingPigment : Process , printingSubstantive : Process - , printingPaste : Process - , printingDyes : Process - , finishing : Process - , passengerCar : Process - , endOfLife : Process - , fading : Process - , heatEurope : Process - , heatRoW : Process + , roadTransport : Process + , seaTransport : Process + , trainTransport : Process , weaving : Process } @@ -71,7 +71,7 @@ getEnnoblingHeatProcess wk country = getPrintingProcess : Printing.Kind -> WellKnown -> { printingProcess : Process, printingToxicityProcess : Process } -getPrintingProcess medium { printingPigment, printingSubstantive, printingDyes, printingPaste } = +getPrintingProcess medium { printingDyes, printingPaste, printingPigment, printingSubstantive } = case medium of Printing.Pigment -> { printingProcess = printingPigment, printingToxicityProcess = printingPaste } @@ -83,64 +83,34 @@ getPrintingProcess medium { printingPigment, printingSubstantive, printingDyes, load : List Process -> Result String WellKnown load processes = let - mapping = - { airTransport = "air-transport" - , bleaching = "bleaching" - , seaTransport = "sea-transport" - , roadTransport = "road-transport" - , trainTransport = "train-transport" - , distribution = "road-transport" - , dyeingYarn = "dyeing-yarn" - , dyeingFabric = "dyeing-fabric" - , dyeingArticle = "dyeing-article" - , dyeingSynthetic = "dyeing-synthetic-fiber" - , dyeingCellulosic = "dyeing-cellulosic-fiber" - , printingPigment = "printing-pigment" - , printingSubstantive = "printing-substantive" - , printingPaste = "printing-paste" - , printingDyes = "printing-dyes" - , finishing = "finishing" - , passengerCar = "passenger-car" - , endOfLife = "end-of-life" - , fading = "fading" - , heatEurope = "heat-europe" - , heatRoW = "heat-row" - , knittingMix = "knitting-mix" - , knittingFullyFashioned = "knitting-fully-fashioned" - , knittingSeamless = "knitting-seamless" - , knittingCircular = "knitting-circular" - , knittingStraight = "knitting-straight" - , weaving = "weaving" - } - - find get = - RE.andMap (Process.findByAlias (Alias <| get mapping) processes) + fromAlias key = + RE.andMap (Process.findByAlias (Alias key) processes) in Ok WellKnown - |> find .airTransport - |> find .bleaching - |> find .seaTransport - |> find .roadTransport - |> find .trainTransport - |> find .distribution - |> find .dyeingYarn - |> find .dyeingFabric - |> find .dyeingArticle - |> find .dyeingSynthetic - |> find .dyeingCellulosic - |> find .knittingMix - |> find .knittingFullyFashioned - |> find .knittingSeamless - |> find .knittingCircular - |> find .knittingStraight - |> find .printingPigment - |> find .printingSubstantive - |> find .printingPaste - |> find .printingDyes - |> find .finishing - |> find .passengerCar - |> find .endOfLife - |> find .fading - |> find .heatEurope - |> find .heatRoW - |> find .weaving + |> fromAlias "air-transport" + |> fromAlias "bleaching" + |> fromAlias "road-transport" + |> fromAlias "dyeing-article" + |> fromAlias "dyeing-cellulosic-fiber" + |> fromAlias "dyeing-fabric" + |> fromAlias "dyeing-synthetic-fiber" + |> fromAlias "dyeing-yarn" + |> fromAlias "end-of-life" + |> fromAlias "fading" + |> fromAlias "finishing" + |> fromAlias "heat-europe" + |> fromAlias "heat-row" + |> fromAlias "knitting-circular" + |> fromAlias "knitting-fully-fashioned" + |> fromAlias "knitting-mix" + |> fromAlias "knitting-seamless" + |> fromAlias "knitting-straight" + |> fromAlias "passenger-car" + |> fromAlias "printing-dyes" + |> fromAlias "printing-paste" + |> fromAlias "printing-pigment" + |> fromAlias "printing-substantive" + |> fromAlias "road-transport" + |> fromAlias "sea-transport" + |> fromAlias "train-transport" + |> fromAlias "weaving" diff --git a/src/Data/Transport.elm b/src/Data/Transport.elm index 6547fb4c2..0270fcc36 100644 --- a/src/Data/Transport.elm +++ b/src/Data/Transport.elm @@ -36,23 +36,23 @@ type alias Distances = type alias Transport = - { road : Length + { air : Length + , impacts : Impacts + , road : Length , roadCooled : Length , sea : Length , seaCooled : Length - , air : Length - , impacts : Impacts } default : Impacts -> Transport default impacts = - { road = Quantity.zero + { air = Quantity.zero + , impacts = impacts + , road = Quantity.zero , roadCooled = Quantity.zero , sea = Quantity.zero , seaCooled = Quantity.zero - , air = Quantity.zero - , impacts = impacts } @@ -60,21 +60,21 @@ erroneous : Impacts -> Transport erroneous impacts = -- FIXME temporary data to display something weird instead of zero, until -- we use of a Result String Transport in the transport computations - { road = Quantity.infinity + { air = Quantity.infinity + , impacts = impacts + , road = Quantity.infinity , roadCooled = Quantity.infinity , sea = Quantity.infinity , seaCooled = Quantity.infinity - , air = Quantity.infinity - , impacts = impacts } add : Transport -> Transport -> Transport add a b = { b - | road = b.road |> Quantity.plus a.road + | air = b.air |> Quantity.plus a.air + , road = b.road |> Quantity.plus a.road , sea = b.sea |> Quantity.plus a.sea - , air = b.air |> Quantity.plus a.air } @@ -116,21 +116,21 @@ computeImpacts { wellKnown } mass transport = sum : List Transport -> Transport sum = List.foldl - (\{ road, roadCooled, sea, seaCooled, air, impacts } acc -> + (\{ air, impacts, road, roadCooled, sea, seaCooled } acc -> { acc - | road = acc.road |> Quantity.plus road + | air = acc.air |> Quantity.plus air + , impacts = Impact.sumImpacts [ acc.impacts, impacts ] + , road = acc.road |> Quantity.plus road , roadCooled = acc.roadCooled |> Quantity.plus roadCooled , sea = acc.sea |> Quantity.plus sea , seaCooled = acc.seaCooled |> Quantity.plus seaCooled - , air = acc.air |> Quantity.plus air - , impacts = Impact.sumImpacts [ acc.impacts, impacts ] } ) (default Impact.empty) totalKm : Transport -> Float -totalKm { road, sea, air } = +totalKm { air, road, sea } = Quantity.sum [ road, sea, air ] |> Length.inKilometers @@ -205,25 +205,25 @@ encodeKm = decode : Decoder Transport decode = Decode.map6 Transport + (Decode.field "air" decodeKm) + (Decode.succeed Impact.empty) (Decode.field "road" decodeKm) -- roadCooled (Decode.succeed Quantity.zero) (Decode.field "sea" decodeKm) -- seaCooled (Decode.succeed Quantity.zero) - (Decode.field "air" decodeKm) - (Decode.succeed Impact.empty) encode : Transport -> Encode.Value encode v = Encode.object - [ ( "road", encodeKm v.road ) + [ ( "air", encodeKm v.air ) + , ( "impacts", Impact.encode v.impacts ) + , ( "road", encodeKm v.road ) , ( "roadCooled", encodeKm v.roadCooled ) , ( "sea", encodeKm v.sea ) , ( "seaCooled", encodeKm v.seaCooled ) - , ( "air", encodeKm v.air ) - , ( "impacts", Impact.encode v.impacts ) ] diff --git a/src/Data/Unit.elm b/src/Data/Unit.elm index 4fc5c7823..c8e33edf9 100644 --- a/src/Data/Unit.elm +++ b/src/Data/Unit.elm @@ -1,26 +1,28 @@ module Data.Unit exposing - ( Durability(..) + ( HolisticDurability , Impact , ImpactUnit(..) + , NonPhysicalDurability(..) + , PhysicalDurability(..) , PickPerMeter(..) , Ratio(..) , SurfaceMass , ThreadDensity(..) , YarnSize - , decodeDurability , decodeImpact + , decodePhysicalDurability , decodeRatio , decodeSurfaceMass , decodeYarnSize - , durability - , durabilityToFloat - , encodeDurability , encodeImpact + , encodeNonPhysicalDurability + , encodePhysicalDurability , encodePickPerMeter , encodeRatio , encodeSurfaceMass , encodeThreadDensity , encodeYarnSize + , floatDurabilityFromHolistic , forKWh , forKg , forKgAndDistance @@ -35,6 +37,10 @@ module Data.Unit exposing , minDurability , minSurfaceMass , minYarnSize + , nonPhysicalDurability + , nonPhysicalDurabilityToFloat + , physicalDurability + , physicalDurabilityToFloat , pickPerMeter , pickPerMeterToFloat , ratio @@ -110,59 +116,89 @@ encodeRatio = -- Durability -type Durability - = Durability Float +type PhysicalDurability + = PhysicalDurability Float -minDurability : Durability -minDurability = - Durability 0.67 +type NonPhysicalDurability + = NonPhysicalDurability Float -standardDurability : Durability -standardDurability = - Durability 1 +type alias HolisticDurability = + { nonPhysical : NonPhysicalDurability + , physical : PhysicalDurability + } -maxDurability : Durability -maxDurability = - Durability 1.45 +minDurability : (Float -> a) -> a +minDurability dur = + dur 0.67 -durability : Float -> Durability -durability = - Durability +standardDurability : (Float -> a) -> a +standardDurability dur = + dur 1 -durabilityToFloat : Durability -> Float -durabilityToFloat (Durability float) = +maxDurability : (Float -> a) -> a +maxDurability dur = + dur 1.45 + + +physicalDurability : Float -> PhysicalDurability +physicalDurability value = + PhysicalDurability value + + +nonPhysicalDurability : Float -> NonPhysicalDurability +nonPhysicalDurability value = + NonPhysicalDurability value + + +floatDurabilityFromHolistic : HolisticDurability -> Float +floatDurabilityFromHolistic { nonPhysical, physical } = + min (physicalDurabilityToFloat physical) (nonPhysicalDurabilityToFloat nonPhysical) + + +physicalDurabilityToFloat : PhysicalDurability -> Float +physicalDurabilityToFloat (PhysicalDurability float) = float -decodeDurability : Decoder Durability -decodeDurability = +nonPhysicalDurabilityToFloat : NonPhysicalDurability -> Float +nonPhysicalDurabilityToFloat (NonPhysicalDurability float) = + float + + +decodePhysicalDurability : Decoder PhysicalDurability +decodePhysicalDurability = Decode.float |> Decode.andThen (\float -> - if float < durabilityToFloat minDurability || float > durabilityToFloat maxDurability then + if float < physicalDurabilityToFloat (minDurability PhysicalDurability) || float > physicalDurabilityToFloat (maxDurability PhysicalDurability) then Decode.fail ("Le coefficient de durabilité spécifié (" ++ String.fromFloat float - ++ ") doit être comprise entre " - ++ String.fromFloat (durabilityToFloat minDurability) + ++ ") doit être compris entre " + ++ String.fromFloat (physicalDurabilityToFloat (minDurability PhysicalDurability)) ++ " et " - ++ String.fromFloat (durabilityToFloat maxDurability) + ++ String.fromFloat (physicalDurabilityToFloat (maxDurability PhysicalDurability)) ++ "." ) else Decode.succeed float ) - |> Decode.map durability + |> Decode.map physicalDurability + + +encodePhysicalDurability : PhysicalDurability -> Encode.Value +encodePhysicalDurability (PhysicalDurability float) = + Encode.float float -encodeDurability : Durability -> Encode.Value -encodeDurability (Durability float) = +encodeNonPhysicalDurability : NonPhysicalDurability -> Encode.Value +encodeNonPhysicalDurability (NonPhysicalDurability float) = Encode.float float diff --git a/src/Data/User.elm b/src/Data/User.elm index 60411ceb8..56a06d4bc 100644 --- a/src/Data/User.elm +++ b/src/Data/User.elm @@ -12,11 +12,11 @@ import Json.Encode as Encode type alias User = - { email : String + { cgu : Bool + , company : String + , email : String , firstname : String , lastname : String - , company : String - , cgu : Bool , token : String } @@ -28,11 +28,11 @@ type alias Form a = decode : Decoder User decode = Decode.succeed User + |> Pipe.required "terms_of_use" Decode.bool + |> Pipe.optional "organization" Decode.string "" |> Pipe.required "email" Decode.string |> Pipe.required "first_name" Decode.string |> Pipe.required "last_name" Decode.string - |> Pipe.optional "organization" Decode.string "" - |> Pipe.required "terms_of_use" Decode.bool |> Pipe.required "token" Decode.string @@ -62,11 +62,11 @@ encodeForm user = form : User -> Form User form user = - { email = user.email + { cgu = user.cgu + , company = user.company + , email = user.email , firstname = user.firstname , lastname = user.lastname - , company = user.company - , cgu = user.cgu - , token = "" , next = "/#/auth/authenticated" + , token = "" } diff --git a/src/Main.elm b/src/Main.elm index f93931abf..887d2be41 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -43,28 +43,28 @@ type alias Flags = type Page = ApiPage Api.Model | AuthPage Auth.Model - | LoadingPage | ChangelogPage Changelog.Model | EditorialPage Editorial.Model | ExplorePage Explore.Model | FoodBuilderPage FoodBuilder.Model | HomePage Home.Model + | LoadingPage | NotFoundPage | StatsPage Stats.Model | TextileSimulatorPage TextileSimulator.Model type State - = Loaded Session Page - | Errored String + = Errored String + | Loaded Session Page type alias Model = - { state : State - , mobileNavigationOpened : Bool + { mobileNavigationOpened : Bool -- Duplicate the nav key in the model so Parcel's hot module reloading finds it always in the same place. , navKey : Nav.Key + , state : State , url : Url } @@ -98,14 +98,23 @@ init : Flags -> Url -> Nav.Key -> ( Model, Cmd Msg ) init flags requestedUrl navKey = setRoute requestedUrl <| case StaticDb.db StaticJson.rawJsonProcesses of + Err err -> + ( { mobileNavigationOpened = False + , navKey = navKey + , state = Errored err + , url = requestedUrl + } + , Cmd.none + ) + Ok db -> let session = setupSession navKey flags db in - ( { state = Loaded session LoadingPage - , mobileNavigationOpened = False + ( { mobileNavigationOpened = False , navKey = navKey + , state = Loaded session LoadingPage , url = requestedUrl } , Cmd.batch @@ -121,15 +130,6 @@ init flags requestedUrl navKey = ] ) - Err err -> - ( { state = Errored err - , mobileNavigationOpened = False - , navKey = navKey - , url = requestedUrl - } - , Cmd.none - ) - setupSession : Nav.Key -> Flags -> Db -> Session setupSession navKey flags db = @@ -137,13 +137,12 @@ setupSession navKey flags db = store = Session.deserializeStore flags.rawStore in - { db = db - , clientUrl = flags.clientUrl - , enableFoodSection = flags.enableFoodSection - , navKey = navKey - , store = store + { clientUrl = flags.clientUrl , currentVersion = Request.Version.Unknown + , db = db + , enableFoodSection = flags.enableFoodSection , matomo = flags.matomo + , navKey = navKey , notifications = [] , queries = { food = FoodQuery.empty @@ -154,12 +153,17 @@ setupSession navKey flags db = |> Result.withDefault TextileQuery.default } , releases = RemoteData.NotAsked + , store = store } setRoute : Url -> ( Model, Cmd Msg ) -> ( Model, Cmd Msg ) setRoute url ( { state } as model, cmds ) = case state of + Errored _ -> + -- FIXME: Static database decoding error, highly unlikely to ever happen + ( model, cmds ) + Loaded session _ -> let -- TODO: factor this with `update` internal `toPage` @@ -181,25 +185,10 @@ setRoute url ( { state } as model, cmds ) = ) in case Route.fromUrl url of - Nothing -> - ( { model | state = Loaded session NotFoundPage }, Cmd.none ) - - Just Route.Home -> - Home.init session - |> toPage HomePage HomeMsg - - Just Route.Api -> - Api.init session - |> toPage ApiPage ApiMsg - Just (Route.Auth data) -> Auth.init session data |> toPage AuthPage AuthMsg - Just Route.Changelog -> - Changelog.init session - |> toPage ChangelogPage ChangelogMsg - Just (Route.Editorial slug) -> Editorial.init slug session |> toPage EditorialPage EditorialMsg @@ -208,10 +197,6 @@ setRoute url ( { state } as model, cmds ) = Explore.init scope dataset session |> toPage ExplorePage ExploreMsg - Just Route.FoodBuilderHome -> - FoodBuilder.init session Impact.default Nothing - |> toPage FoodBuilderPage FoodBuilderMsg - Just (Route.FoodBuilder trigram maybeQuery) -> FoodBuilder.init session trigram maybeQuery |> toPage FoodBuilderPage FoodBuilderMsg @@ -220,14 +205,6 @@ setRoute url ( { state } as model, cmds ) = FoodBuilder.initFromExample session uuid |> toPage FoodBuilderPage FoodBuilderMsg - Just Route.Stats -> - Stats.init session - |> toPage StatsPage StatsMsg - - Just Route.TextileSimulatorHome -> - TextileSimulator.init Impact.default Nothing session - |> toPage TextileSimulatorPage TextileSimulatorMsg - Just (Route.TextileSimulator trigram maybeQuery) -> TextileSimulator.init trigram maybeQuery session |> toPage TextileSimulatorPage TextileSimulatorMsg @@ -236,9 +213,32 @@ setRoute url ( { state } as model, cmds ) = TextileSimulator.initFromExample session uuid |> toPage TextileSimulatorPage TextileSimulatorMsg - Errored _ -> - -- FIXME: Static database decoding error, highly unlikely to ever happen - ( model, cmds ) + Just Route.Api -> + Api.init session + |> toPage ApiPage ApiMsg + + Just Route.Changelog -> + Changelog.init session + |> toPage ChangelogPage ChangelogMsg + + Just Route.FoodBuilderHome -> + FoodBuilder.init session Impact.default Nothing + |> toPage FoodBuilderPage FoodBuilderMsg + + Just Route.Home -> + Home.init session + |> toPage HomePage HomeMsg + + Just Route.Stats -> + Stats.init session + |> toPage StatsPage StatsMsg + + Just Route.TextileSimulatorHome -> + TextileSimulator.init Impact.default Nothing session + |> toPage TextileSimulatorPage TextileSimulatorMsg + + Nothing -> + ( { model | state = Loaded session NotFoundPage }, Cmd.none ) update : Msg -> Model -> ( Model, Cmd Msg ) @@ -281,13 +281,13 @@ update rawMsg ({ state } as model) = ( DetailedProcessesReceived url (Ok rawDetailedProcessesJson), currentPage ) -> -- When detailed processes are received, rebuild the entire static db using them case StaticDb.db rawDetailedProcessesJson of + Err error -> + ( { model | state = Errored error }, Cmd.none ) + Ok detailedDb -> { model | state = currentPage |> Loaded { session | db = detailedDb } } |> update (UrlChanged url) - Err error -> - ( { model | state = Errored error }, Cmd.none ) - ( DetailedProcessesReceived _ (Err httpError), _ ) -> ( { model | state = Errored (Request.Common.errorToString httpError) } , Cmd.none @@ -430,14 +430,14 @@ subscriptions { state } = view : Model -> Document Msg -view { state, mobileNavigationOpened } = +view { mobileNavigationOpened, state } = case state of Errored error -> - { title = "Erreur lors du chargement…" - , body = + { body = [ Html.p [] [ Html.text <| "Database couldn't be parsed: " ] , Html.pre [] [ Html.text error ] ] + , title = "Erreur lors du chargement…" } Loaded session page -> @@ -456,11 +456,6 @@ view { state, mobileNavigationOpened } = ( title, content |> List.map (Html.map msg) ) in case page of - HomePage homeModel -> - Home.view session homeModel - |> mapMsg HomeMsg - |> Page.frame (pageConfig Page.Home) - ApiPage examplesModel -> Api.view session examplesModel |> mapMsg ApiMsg @@ -491,32 +486,37 @@ view { state, mobileNavigationOpened } = |> mapMsg FoodBuilderMsg |> Page.frame (pageConfig Page.FoodBuilder) - TextileSimulatorPage simulatorModel -> - TextileSimulator.view session simulatorModel - |> mapMsg TextileSimulatorMsg - |> Page.frame (pageConfig Page.TextileSimulator) + HomePage homeModel -> + Home.view session homeModel + |> mapMsg HomeMsg + |> Page.frame (pageConfig Page.Home) - StatsPage statsModel -> - Stats.view session statsModel - |> mapMsg StatsMsg - |> Page.frame (pageConfig Page.Stats) + LoadingPage -> + ( "Chargement…", [ Page.loading ] ) + |> Page.frame (pageConfig Page.Other) NotFoundPage -> ( "Page manquante", [ Page.notFound ] ) |> Page.frame (pageConfig Page.Other) - LoadingPage -> - ( "Chargement…", [ Page.loading ] ) - |> Page.frame (pageConfig Page.Other) + StatsPage statsModel -> + Stats.view session statsModel + |> mapMsg StatsMsg + |> Page.frame (pageConfig Page.Stats) + + TextileSimulatorPage simulatorModel -> + TextileSimulator.view session simulatorModel + |> mapMsg TextileSimulatorMsg + |> Page.frame (pageConfig Page.TextileSimulator) main : Program Flags Model Msg main = Browser.application { init = init - , view = view - , update = update - , subscriptions = subscriptions , onUrlChange = UrlChanged , onUrlRequest = UrlRequested + , subscriptions = subscriptions + , update = update + , view = view } diff --git a/src/Page/Api.elm b/src/Page/Api.elm index ddc1089a1..7d4acd6eb 100644 --- a/src/Page/Api.elm +++ b/src/Page/Api.elm @@ -55,7 +55,12 @@ getApiServerUrl { clientUrl } = changelog : List News changelog = - [ { date = "29 août 2024" + [ { date = "5 septembre 2024" + , level = "minor" + , domains = [ "Textile" ] + , md = "Ajout du champ `physicalDurability` qui permet de préciser la durabilité physique d'un vêtement." + } + , { date = "29 août 2024" , level = "minor" , domains = [ "Textile" ] , md = """Ajout du champ booléen `upcycled` qui permet de préciser si un vêtement est remanufacturé. diff --git a/src/Page/Auth.elm b/src/Page/Auth.elm index 36b7de455..35e6fb6a7 100644 --- a/src/Page/Auth.elm +++ b/src/Page/Auth.elm @@ -44,8 +44,8 @@ type Msg type Tab - = RegistrationTab - | AuthenticationTab + = AuthenticationTab + | RegistrationTab init : Session -> { authenticated : Bool } -> ( Model, Session, Cmd Msg ) @@ -306,11 +306,11 @@ viewLoginRegisterForm model = ] , div [ class "card-body" ] [ case model.currentTab of - RegistrationTab -> - viewRegistrationForm model - AuthenticationTab -> viewLoginForm model + + RegistrationTab -> + viewRegistrationForm model ] ] diff --git a/src/Page/Changelog.elm b/src/Page/Changelog.elm index be2c39881..0b80eea06 100644 --- a/src/Page/Changelog.elm +++ b/src/Page/Changelog.elm @@ -57,14 +57,14 @@ view _ model = , [ Container.centered [ class "pb-5" ] [ h1 [ class "mb-3" ] [ text "Changelog" ] , case model.releases of + RemoteData.Failure error -> + Alert.httpError error + RemoteData.Success releases -> releases |> List.map (.markdown >> Markdown.simple []) |> div [] - RemoteData.Failure error -> - Alert.httpError error - _ -> SpinnerView.view ] diff --git a/src/Page/Editorial.elm b/src/Page/Editorial.elm index 354847195..d74ff45f1 100644 --- a/src/Page/Editorial.elm +++ b/src/Page/Editorial.elm @@ -55,6 +55,15 @@ update session msg model = view : Session -> Model -> ( String, List (Html Msg) ) view _ model = case model.content of + RemoteData.Failure httpError -> + ( "Erreur de chargement", [ Alert.httpError httpError ] ) + + RemoteData.Loading -> + ( "Chargement…", [ Spinner.view ] ) + + RemoteData.NotAsked -> + ( "", [] ) + RemoteData.Success content -> ( content |> String.split "\n" @@ -66,12 +75,3 @@ view _ model = ] ] ) - - RemoteData.Loading -> - ( "Chargement…", [ Spinner.view ] ) - - RemoteData.Failure httpError -> - ( "Erreur de chargement", [ Alert.httpError httpError ] ) - - RemoteData.NotAsked -> - ( "", [] ) diff --git a/src/Page/Explore.elm b/src/Page/Explore.elm index 4f55af514..e1b156734 100644 --- a/src/Page/Explore.elm +++ b/src/Page/Explore.elm @@ -60,8 +60,8 @@ type alias Model = type Msg - = NoOp - | CloseModal + = CloseModal + | NoOp | OpenDetail Route | ScopeChange Scope | SetTableState SortableTable.State @@ -75,9 +75,6 @@ init scope dataset session = Dataset.Countries _ -> "Nom" - Dataset.Impacts _ -> - "Code" - Dataset.FoodExamples _ -> "Coût Environnemental" @@ -87,17 +84,20 @@ init scope dataset session = Dataset.FoodProcesses _ -> "Nom" + Dataset.Impacts _ -> + "Code" + Dataset.TextileExamples _ -> "Coût Environnemental" - Dataset.TextileProducts _ -> - "Identifiant" - Dataset.TextileMaterials _ -> "Identifiant" Dataset.TextileProcesses _ -> "Nom" + + Dataset.TextileProducts _ -> + "Identifiant" in ( { dataset = dataset , scope = scope @@ -111,9 +111,6 @@ init scope dataset session = update : Session -> Msg -> Model -> ( Model, Session, Cmd Msg ) update session msg model = case msg of - NoOp -> - ( model, session, Cmd.none ) - CloseModal -> ( model , session @@ -124,6 +121,9 @@ update session msg model = |> Nav.pushUrl session.navKey ) + NoOp -> + ( model, session, Cmd.none ) + OpenDetail route -> ( model , session @@ -248,12 +248,12 @@ countriesExplorer { distances, countries } tableConfig tableState scope maybeCod Just code -> detailsModal (case Country.findByCode code countries of + Err error -> + alert error + Ok country -> country |> Table.viewDetails scope (ExploreCountries.table distances countries) - - Err error -> - alert error ) Nothing -> @@ -321,6 +321,9 @@ foodExamplesExplorer db tableConfig tableState maybeId = Just id -> detailsModal (case Example.findByUuid id db.food.examples of + Err error -> + alert error + Ok example -> Table.viewDetails Scope.Food (FoodExamples.table max) @@ -329,9 +332,6 @@ foodExamplesExplorer db tableConfig tableState maybeId = , per100g = getFoodScorePer100g db example } ) - - Err error -> - alert error ) Nothing -> @@ -353,11 +353,11 @@ foodIngredientsExplorer { food } tableConfig tableState maybeId = Just id -> detailsModal (case Ingredient.findByID id food.ingredients of - Ok ingredient -> - foodIngredientDetails food ingredient - Err error -> alert error + + Ok ingredient -> + foodIngredientDetails food ingredient ) Nothing -> @@ -384,12 +384,12 @@ foodProcessesExplorer { food } tableConfig tableState maybeId = Just id -> detailsModal (case FoodProcess.findByIdentifier id food.processes of + Err error -> + alert error + Ok process -> process |> Table.viewDetails Scope.Food (FoodProcesses.table food) - - Err error -> - alert error ) Nothing -> @@ -437,6 +437,9 @@ textileExamplesExplorer db tableConfig tableState maybeId = Just id -> detailsModal (case Example.findByUuid id db.textile.examples of + Err error -> + alert error + Ok example -> Table.viewDetails Scope.Textile (TextileExamples.table max) @@ -445,9 +448,6 @@ textileExamplesExplorer db tableConfig tableState maybeId = , per100g = getTextileScorePer100g db example } ) - - Err error -> - alert error ) Nothing -> @@ -468,11 +468,11 @@ textileProductsExplorer db tableConfig tableState maybeId = Just id -> detailsModal (case Product.findById id db.textile.products of - Ok product -> - Table.viewDetails Scope.Textile (TextileProducts.table db) product - Err error -> alert error + + Ok product -> + Table.viewDetails Scope.Textile (TextileProducts.table db) product ) Nothing -> @@ -493,11 +493,11 @@ textileMaterialsExplorer db tableConfig tableState maybeId = Just id -> detailsModal (case Material.findById id db.textile.materials of - Ok material -> - textileMaterialDetails db material - Err error -> alert error + + Ok material -> + textileMaterialDetails db material ) Nothing -> @@ -523,12 +523,12 @@ textileProcessesExplorer { textile } tableConfig tableState maybeId = Just id -> detailsModal (case Process.findByUuid id textile.processes of + Err error -> + alert error + Ok process -> process |> Table.viewDetails Scope.Textile TextileProcesses.table - - Err error -> - alert error ) Nothing -> @@ -603,9 +603,6 @@ explore { db } { scope, dataset, tableState } = Dataset.Countries maybeCode -> countriesExplorer db tableConfig tableState scope maybeCode - Dataset.Impacts maybeTrigram -> - impactsExplorer db.definitions tableConfig tableState scope maybeTrigram - Dataset.FoodExamples maybeId -> foodExamplesExplorer db tableConfig tableState maybeId @@ -615,18 +612,21 @@ explore { db } { scope, dataset, tableState } = Dataset.FoodProcesses maybeId -> foodProcessesExplorer db tableConfig tableState maybeId + Dataset.Impacts maybeTrigram -> + impactsExplorer db.definitions tableConfig tableState scope maybeTrigram + Dataset.TextileExamples maybeId -> textileExamplesExplorer db tableConfig tableState maybeId Dataset.TextileMaterials maybeId -> textileMaterialsExplorer db tableConfig tableState maybeId - Dataset.TextileProducts maybeId -> - textileProductsExplorer db tableConfig tableState maybeId - Dataset.TextileProcesses maybeId -> textileProcessesExplorer db tableConfig tableState maybeId + Dataset.TextileProducts maybeId -> + textileProductsExplorer db tableConfig tableState maybeId + view : Session -> Model -> ( String, List (Html Msg) ) view session model = diff --git a/src/Page/Explore/FoodProcesses.elm b/src/Page/Explore/FoodProcesses.elm index 9de9a8bf7..e4223d08c 100644 --- a/src/Page/Explore/FoodProcesses.elm +++ b/src/Page/Explore/FoodProcesses.elm @@ -28,8 +28,8 @@ table _ { detailed, scope } = [ code [] [ text (FoodProcess.identifierToString process.identifier) ] ] } , { label = "Nom" - , toValue = Table.StringValue getDisplayName - , toCell = getDisplayName >> text + , toValue = Table.StringValue FoodProcess.getDisplayName + , toCell = FoodProcess.getDisplayName >> text } , { label = "Catégorie" , toValue = Table.StringValue <| .category >> FoodProcess.categoryToLabel @@ -43,10 +43,6 @@ table _ { detailed, scope } = , toValue = Table.StringValue <| .source , toCell = .source >> text } - , { label = "Identifiant source" - , toValue = Table.StringValue <| .identifier >> FoodProcess.identifierToString - , toCell = \process -> code [] [ text (FoodProcess.identifierToString process.identifier) ] - } , { label = "Unité" , toValue = Table.StringValue <| .unit , toCell = .unit >> text @@ -61,13 +57,3 @@ table _ { detailed, scope } = } ] } - - -getDisplayName : FoodProcess.Process -> String -getDisplayName process = - case process.displayName of - Just displayName -> - displayName - - Nothing -> - FoodProcess.nameToString process.name diff --git a/src/Page/Explore/Table.elm b/src/Page/Explore/Table.elm index bcba51983..0edf2cabd 100644 --- a/src/Page/Explore/Table.elm +++ b/src/Page/Explore/Table.elm @@ -37,8 +37,8 @@ type alias Column data comparable msg = type Value comparable data = FloatValue (data -> Float) | IntValue (data -> Int) - | StringValue (data -> String) | NoValue + | StringValue (data -> String) type alias Config data msg = @@ -169,8 +169,8 @@ valueToString item toValue = IntValue getInt -> getInt item |> String.fromInt - StringValue getString -> - getString item - NoValue -> "N/A" + + StringValue getString -> + getString item diff --git a/src/Page/Explore/TextileMaterials.elm b/src/Page/Explore/TextileMaterials.elm index fa2ab7ea9..0ac362b89 100644 --- a/src/Page/Explore/TextileMaterials.elm +++ b/src/Page/Explore/TextileMaterials.elm @@ -94,9 +94,6 @@ table db { detailed, scope } = , toCell = \material -> case Country.findByCode material.defaultCountry db.countries of - Ok country -> - text country.name - Err error -> Alert.simple { level = Alert.Danger @@ -104,6 +101,9 @@ table db { detailed, scope } = , title = Nothing , content = [ text error ] } + + Ok country -> + text country.name } , { label = "CFF: Coefficient d'allocation" , toValue = diff --git a/src/Page/Explore/TextileProcesses.elm b/src/Page/Explore/TextileProcesses.elm index 51ec6e09c..73919e198 100644 --- a/src/Page/Explore/TextileProcesses.elm +++ b/src/Page/Explore/TextileProcesses.elm @@ -16,11 +16,7 @@ table { detailed, scope } = , toRoute = .uuid >> Just >> Dataset.TextileProcesses >> Route.Explore scope , legend = [] , columns = - [ { label = "Étape" - , toValue = Table.StringValue <| .stepUsage - , toCell = .stepUsage >> text - } - , { label = "Identifiant" + [ { label = "Identifiant" , toValue = Table.StringValue <| .uuid >> Process.uuidToString , toCell = .uuid @@ -35,6 +31,14 @@ table { detailed, scope } = ) } , { label = "Nom" + , toValue = Table.StringValue Process.getDisplayName + , toCell = Process.getDisplayName >> text + } + , { label = "Étape" + , toValue = Table.StringValue .stepUsage + , toCell = .stepUsage >> text + } + , { label = "Nom technique" , toValue = Table.StringValue .name , toCell = .name >> text } @@ -44,15 +48,15 @@ table { detailed, scope } = \process -> span [ title process.source ] [ text process.source ] } + , { label = "Unité" + , toValue = Table.StringValue .unit + , toCell = .unit >> text + } , { label = "Correctif" , toValue = Table.StringValue .correctif , toCell = \process -> span [ title process.correctif ] [ text process.correctif ] } - , { label = "Unité" - , toValue = Table.StringValue .unit - , toCell = .unit >> text - } ] } diff --git a/src/Page/Food.elm b/src/Page/Food.elm index 46c15e42e..248c82440 100644 --- a/src/Page/Food.elm +++ b/src/Page/Food.elm @@ -78,19 +78,19 @@ type alias Model = type Modal - = NoModal - | AddIngredientModal (Maybe Recipe.RecipeIngredient) (Autocomplete Ingredient) + = AddIngredientModal (Maybe Recipe.RecipeIngredient) (Autocomplete Ingredient) | ComparatorModal | ExplorerDetailsModal Ingredient + | NoModal | SelectExampleModal (Autocomplete Query) type Msg - = AddIngredient Ingredient + = AddDistribution + | AddIngredient Ingredient | AddPackaging | AddPreparation | AddTransform - | AddDistribution | CopyToClipBoard String | DeleteBookmark Bookmark | DeleteIngredient Ingredient.Id @@ -104,8 +104,8 @@ type Msg | OnStepClick String | OpenComparator | Reset - | ResetTransform | ResetDistribution + | ResetTransform | SaveBookmark | SaveBookmarkWithTime String Bookmark.Query Posix | SelectAllBookmarks @@ -117,11 +117,11 @@ type Msg | SwitchImpactsTab ImpactTabs.Tab | ToggleComparedSimulation Bookmark Bool | UpdateBookmarkName String + | UpdateDistribution String | UpdateIngredient Query.IngredientQuery Query.IngredientQuery | UpdatePackaging Process.Identifier Query.ProcessQuery | UpdatePreparation Preparation.Id Preparation.Id | UpdateTransform Query.ProcessQuery - | UpdateDistribution String init : Session -> Definition.Trigram -> Maybe Query -> ( Model, Session, Cmd Msg ) @@ -149,11 +149,11 @@ init session trigram maybeQuery = } , session |> Session.updateFoodQuery query , case maybeQuery of - Nothing -> - Ports.scrollTo { x = 0, y = 0 } - Just _ -> Cmd.none + + Nothing -> + Ports.scrollTo { x = 0, y = 0 } ) @@ -195,6 +195,10 @@ update ({ db, queries } as session) msg model = |> Maybe.withDefault ( model, session, Cmd.none ) in case msg of + AddDistribution -> + ( model, session, Cmd.none ) + |> updateQuery (Query.setDistribution Retail.ambient query) + AddIngredient ingredient -> update session (SetModal NoModal) model |> updateQuery (query |> Query.addIngredient (Recipe.ingredientQueryFromIngredient ingredient)) @@ -239,10 +243,6 @@ update ({ db, queries } as session) msg model = firstTransform |> maybeUpdateQuery (\transform -> Query.setTransform transform query) - AddDistribution -> - ( model, session, Cmd.none ) - |> updateQuery (Query.setDistribution Retail.ambient query) - CopyToClipBoard shareableLink -> ( model, session, Ports.copyToClipboard shareableLink ) @@ -365,12 +365,6 @@ update ({ db, queries } as session) msg model = SelectNoBookmarks -> ( model, Session.selectNoBookmarks session, Cmd.none ) - SetModal NoModal -> - ( { model | modal = NoModal } - , session - , commandsForNoModal model.modal - ) - SetModal ComparatorModal -> ( { model | modal = ComparatorModal } , session @@ -393,6 +387,12 @@ update ({ db, queries } as session) msg model = ] ) + SetModal NoModal -> + ( { model | modal = NoModal } + , session + , commandsForNoModal model.modal + ) + SetModal (SelectExampleModal autocomplete) -> ( { model | modal = SelectExampleModal autocomplete } , session @@ -1349,21 +1349,21 @@ mainView ({ db } as session) model = } } , case computed of - Ok ( recipe, results ) -> - stepListView db session model recipe results - Err error -> errorView error + + Ok ( recipe, results ) -> + stepListView db session model recipe results , session.queries.food |> debugQueryView db ] , div [ class "col-lg-4 d-flex flex-column gap-3" ] [ case computed of - Ok ( _, results ) -> - sidebarView session model results - Err error -> errorView error + + Ok ( _, results ) -> + sidebarView session model results ] ] @@ -1514,8 +1514,23 @@ view session model = , [ Container.centered [ class "pb-3" ] [ mainView session model , case model.modal of - NoModal -> - text "" + AddIngredientModal _ autocompleteState -> + AutocompleteSelectorView.view + { autocompleteState = autocompleteState + , closeModal = SetModal NoModal + , footer = [] + , noOp = NoOp + , onAutocomplete = OnAutocompleteIngredient + , onAutocompleteSelect = OnAutocompleteSelect + , placeholderText = "tapez ici le nom de la matière première pour la rechercher" + , title = "Sélectionnez un ingrédient" + , toLabel = .name + , toCategory = + .categories + >> List.head + >> Maybe.map IngredientCategory.toLabel + >> Maybe.withDefault "" + } ComparatorModal -> ModalView.view @@ -1551,23 +1566,8 @@ view session model = , footer = [] } - AddIngredientModal _ autocompleteState -> - AutocompleteSelectorView.view - { autocompleteState = autocompleteState - , closeModal = SetModal NoModal - , footer = [] - , noOp = NoOp - , onAutocomplete = OnAutocompleteIngredient - , onAutocompleteSelect = OnAutocompleteSelect - , placeholderText = "tapez ici le nom de la matière première pour la rechercher" - , title = "Sélectionnez un ingrédient" - , toLabel = .name - , toCategory = - .categories - >> List.head - >> Maybe.map IngredientCategory.toLabel - >> Maybe.withDefault "" - } + NoModal -> + text "" SelectExampleModal autocompleteState -> AutocompleteSelectorView.view diff --git a/src/Page/Home.elm b/src/Page/Home.elm index 500b8820e..b2272ffe1 100644 --- a/src/Page/Home.elm +++ b/src/Page/Home.elm @@ -37,8 +37,8 @@ type Msg type Modal = CalculatorPickerModal - | PresentationVideoModal | NoModal + | PresentationVideoModal init : Session -> ( Model, Session, Cmd Msg ) @@ -101,9 +101,6 @@ viewHero { enableFoodSection } modal = ] ] , case modal of - NoModal -> - text "" - CalculatorPickerModal -> ModalView.view { size = ModalView.Large @@ -116,6 +113,9 @@ viewHero { enableFoodSection } modal = , footer = [] } + NoModal -> + text "" + PresentationVideoModal -> ModalView.view { size = ModalView.ExtraLarge diff --git a/src/Page/Stats.elm b/src/Page/Stats.elm index ec3f7bd64..5fcfdaacd 100644 --- a/src/Page/Stats.elm +++ b/src/Page/Stats.elm @@ -28,8 +28,8 @@ type alias Model = type Msg = ApiStats (WebData (List Matomo.Stat)) - | WebStats (WebData (List Matomo.Stat)) | ToggleMode Mode + | WebStats (WebData (List Matomo.Stat)) type Mode @@ -58,24 +58,24 @@ update session msg model = ApiStats apiStats -> ( { model | apiStats = apiStats }, session, Cmd.none ) - WebStats webStats -> - ( { model | webStats = webStats }, session, Cmd.none ) - ToggleMode mode -> ( { model | mode = mode }, session, Cmd.none ) + WebStats webStats -> + ( { model | webStats = webStats }, session, Cmd.none ) + viewStats : { heading : String, unit : String } -> WebData (List Matomo.Stat) -> Html Msg viewStats { heading, unit } webData = case webData of - RemoteData.NotAsked -> - text "" + RemoteData.Failure err -> + Alert.httpError err RemoteData.Loading -> Spinner.view - RemoteData.Failure err -> - Alert.httpError err + RemoteData.NotAsked -> + text "" RemoteData.Success stats -> node "chart-stats" @@ -112,14 +112,6 @@ view { matomo } { mode, apiStats, webStats } = ] , div [ class "border border-top-0 rounded p-2" ] [ case mode of - Simple -> - div [] - [ webStats - |> viewStats { heading = "Fréquentation", unit = "visite" } - , apiStats - |> viewStats { heading = "Traffic sur l'API", unit = "requête" } - ] - Advanced -> let matomoBaseUrl = @@ -190,6 +182,14 @@ view { matomo } { mode, apiStats, webStats } = [] ] ] + + Simple -> + div [] + [ webStats + |> viewStats { heading = "Fréquentation", unit = "visite" } + , apiStats + |> viewStats { heading = "Traffic sur l'API", unit = "requête" } + ] ] ] ] diff --git a/src/Page/Textile.elm b/src/Page/Textile.elm index 67241006a..fd0835cf1 100644 --- a/src/Page/Textile.elm +++ b/src/Page/Textile.elm @@ -66,6 +66,7 @@ import Views.Icon as Icon import Views.ImpactTabs as ImpactTabs import Views.Markdown as Markdown import Views.Modal as ModalView +import Views.RangeSlider as RangeSlider import Views.Sidebar as SidebarView import Views.Textile.Step as StepView @@ -84,11 +85,11 @@ type alias Model = type Modal - = NoModal - | AddMaterialModal (Maybe Inputs.MaterialInput) (Autocomplete Material) + = AddMaterialModal (Maybe Inputs.MaterialInput) (Autocomplete Material) | ComparatorModal | ConfirmSwitchToRegulatoryModal | ExplorerDetailsTab Material + | NoModal | SelectExampleModal (Autocomplete Query) | SelectProductModal (Autocomplete Product) @@ -131,12 +132,13 @@ type Msg | UpdateDyeingMedium DyeingMedium | UpdateFabricProcess Fabric | UpdateMakingComplexity MakingComplexity - | UpdateMakingWaste (Maybe Split) | UpdateMakingDeadStock (Maybe Split) + | UpdateMakingWaste (Maybe Split) | UpdateMassInput String | UpdateMaterial MaterialQuery MaterialQuery | UpdateMaterialSpinning Material Spinning | UpdateNumberOfReferences (Maybe Int) + | UpdatePhysicalDurability (Maybe Unit.PhysicalDurability) | UpdatePrice (Maybe Economics.Price) | UpdatePrinting (Maybe Printing) | UpdateStepCountry Label Country.Code @@ -189,15 +191,15 @@ init trigram maybeUrlQuery session = identity ) , case maybeUrlQuery of - -- If we don't have an URL query, we may be coming from another app page, so we should - -- reposition the viewport at the top. - Nothing -> - Ports.scrollTo { x = 0, y = 0 } - -- If we do have an URL query, we either come from a bookmark, a saved simulation click or -- we're tweaking params for the current simulation: we shouldn't reposition the viewport. Just _ -> Cmd.none + + -- If we don't have an URL query, we may be coming from another app page, so we should + -- reposition the viewport at the top. + Nothing -> + Ports.scrollTo { x = 0, y = 0 } ) @@ -533,6 +535,10 @@ update ({ queries, navKey } as session) msg model = ( model, session, Cmd.none ) |> updateQuery { query | fabricProcess = Just fabricProcess } + ( UpdatePhysicalDurability physicalDurability, _ ) -> + ( model, session, Cmd.none ) + |> updateQuery { query | physicalDurability = physicalDurability } + ( UpdateMakingComplexity makingComplexity, _ ) -> ( model, session, Cmd.none ) |> updateQuery { query | makingComplexity = Just makingComplexity } @@ -782,6 +788,19 @@ productPriceField productPrice = ] +physicalDurabilityField : Unit.PhysicalDurability -> Html Msg +physicalDurabilityField durability = + div [ class "input-group" ] + [ RangeSlider.physicalDurability + { id = "physicalDurability" + , update = UpdatePhysicalDurability + , value = durability + , toString = Unit.physicalDurabilityToFloat >> String.fromFloat + , disabled = False + } + ] + + businessField : Economics.Business -> Html Msg businessField business = [ Economics.SmallBusiness @@ -932,11 +951,11 @@ simulatorFormView session model ({ inputs } as simulator) = ] , div [ class "card shadow-sm pb-2 mb-3" ] [ div [ class "card-header d-flex justify-content-between align-items-center" ] - [ h2 [ class "h5 mb-1 text-truncate" ] [ text "Durabilité non-physique" ] + [ h2 [ class "h5 mb-1 text-truncate" ] [ text "Durabilité" ] , div [ class "d-flex align-items-center gap-2" ] [ span [ class "d-none d-sm-flex text-truncate" ] [ text "Coefficient de durabilité\u{00A0}:" ] , simulator.durability - |> Unit.durabilityToFloat + |> Unit.floatDurabilityFromHolistic |> Format.formatFloat 2 |> text , Button.docsPillLink @@ -994,6 +1013,25 @@ simulatorFormView session model ({ inputs } as simulator) = |> traceabilityField ] ] + , if model.activeTab == ExploratoryTab then + div [] + [ div [ class "card-body py-2 row g-3 align-items-start flex-md-columns" ] + [ div [ class "col-md-4" ] [ text "Durabilité non physique" ] + , div [ class "col-md-8" ] + [ simulator.durability.nonPhysical + |> Unit.nonPhysicalDurabilityToFloat + |> String.fromFloat + |> text + ] + ] + , div [ class "card-body py-2 row g-3 align-items-start flex-md-columns" ] + [ div [ class "col-md-4" ] [ text "Durabilité physique" ] + , div [ class "col-md-8" ] [ physicalDurabilityField simulator.durability.physical ] + ] + ] + + else + text "" ] , div [] [ lifeCycleStepsView session.db model simulator @@ -1109,12 +1147,18 @@ view session model = ( "Simulateur" , [ Container.centered [ class "Simulator pb-3" ] (case model.simulator of + Err error -> + [ Alert.simple + { level = Alert.Danger + , close = Nothing + , title = Just "Erreur" + , content = [ text error ] + } + ] + Ok simulator -> [ simulatorView session model simulator , case model.modal of - NoModal -> - text "" - AddMaterialModal _ autocompleteState -> AutocompleteSelector.view { autocompleteState = autocompleteState @@ -1192,6 +1236,9 @@ view session model = , footer = [] } + NoModal -> + text "" + SelectExampleModal autocompleteState -> AutocompleteSelector.view { autocompleteState = autocompleteState @@ -1229,15 +1276,6 @@ view session model = , toCategory = always "" } ] - - Err error -> - [ Alert.simple - { level = Alert.Danger - , close = Nothing - , title = Just "Erreur" - , content = [ text error ] - } - ] ) ] ) diff --git a/src/Request/Auth.elm b/src/Request/Auth.elm index df77a5265..4a0651cac 100644 --- a/src/Request/Auth.elm +++ b/src/Request/Auth.elm @@ -23,8 +23,8 @@ type alias Errors = type AuthResponse - = SuccessResponse String - | ErrorResponse String Errors + = ErrorResponse String Errors + | SuccessResponse String decodeAuthResponse : Decoder AuthResponse @@ -46,46 +46,46 @@ decodeAuthResponse = login : (Result Http.Error AuthResponse -> msg) -> String -> Cmd msg login event email = Http.post - { url = "/accounts/login/" - , body = Http.jsonBody (Encode.object [ ( "email", Encode.string email ) ]) + { body = Http.jsonBody (Encode.object [ ( "email", Encode.string email ) ]) , expect = Http.expectJson event decodeAuthResponse + , url = "/accounts/login/" } logout : msg -> Cmd msg logout event = Http.post - { url = "/accounts/logout/" - , body = Http.emptyBody + { body = Http.emptyBody , expect = Http.expectWhatever (always event) + , url = "/accounts/logout/" } processes : (Result Http.Error RawJsonProcesses -> msg) -> String -> Cmd msg processes event token = Http.request - { method = "GET" - , url = "processes/processes.json" - , headers = [ Http.header "token" token ] - , body = Http.emptyBody + { body = Http.emptyBody , expect = Http.expectJson event Db.decodeRawJsonProcesses + , headers = [ Http.header "token" token ] + , method = "GET" , timeout = Nothing , tracker = Nothing + , url = "processes/processes.json" } profile : (Result Http.Error User -> msg) -> Cmd msg profile event = Http.get - { url = "/accounts/profile/" - , expect = Http.expectJson event User.decode + { expect = Http.expectJson event User.decode + , url = "/accounts/profile/" } register : (Result Http.Error AuthResponse -> msg) -> Encode.Value -> Cmd msg register event userForm = Http.post - { url = "/accounts/register/" - , body = Http.jsonBody userForm + { body = Http.jsonBody userForm , expect = Http.expectJson event decodeAuthResponse + , url = "/accounts/register/" } diff --git a/src/Request/Common.elm b/src/Request/Common.elm index 79bd9d8f0..7658e756d 100644 --- a/src/Request/Common.elm +++ b/src/Request/Common.elm @@ -6,17 +6,17 @@ import Http errorToString : Http.Error -> String errorToString error = case error of + Http.BadBody body -> + "Échec de l'interprétation de la réponse HTTP: " ++ body + + Http.BadStatus status_code -> + "Erreur HTTP " ++ String.fromInt status_code + Http.BadUrl url -> "URL invalide: " ++ url - Http.Timeout -> - "Délai dépassé." - Http.NetworkError -> "Erreur de communication réseau. Êtes-vous connecté ?" - Http.BadStatus status_code -> - "Erreur HTTP " ++ String.fromInt status_code - - Http.BadBody body -> - "Échec de l'interprétation de la réponse HTTP: " ++ body + Http.Timeout -> + "Délai dépassé." diff --git a/src/Request/Matomo.elm b/src/Request/Matomo.elm index 6c8fc4181..4f69605a5 100644 --- a/src/Request/Matomo.elm +++ b/src/Request/Matomo.elm @@ -9,10 +9,10 @@ import RemoteData exposing (WebData) getStats : String -> String -> String -> (WebData (List Matomo.Stat) -> msg) -> Cmd msg getStats host jsonKey qs event = Http.get - { url = "https://" ++ host ++ "/" ++ qs - , expect = + { expect = Matomo.decodeStats jsonKey |> Http.expectJson (RemoteData.fromResult >> event) + , url = "https://" ++ host ++ "/" ++ qs } diff --git a/src/Request/Version.elm b/src/Request/Version.elm index b09cdd36b..207120e01 100644 --- a/src/Request/Version.elm +++ b/src/Request/Version.elm @@ -33,6 +33,9 @@ updateVersion currentVersion webData = case webData of RemoteData.Success v -> case currentVersion of + NewerVersion -> + currentVersion + Version currentV -> if currentV.hash /= v.hash || currentV.tag /= v.tag then NewerVersion @@ -40,9 +43,6 @@ updateVersion currentVersion webData = else currentVersion - NewerVersion -> - currentVersion - _ -> Version v diff --git a/src/Route.elm b/src/Route.elm index fd2a76568..46965d0b6 100644 --- a/src/Route.elm +++ b/src/Route.elm @@ -20,19 +20,19 @@ import Url.Parser as Parser exposing ((), Parser) type Route - = Home - | Api + = Api | Auth { authenticated : Bool } | Changelog | Editorial String | Explore Scope Dataset | FoodBuilder Definition.Trigram (Maybe FoodQuery.Query) - | FoodBuilderHome | FoodBuilderExample Uuid - | TextileSimulatorHome + | FoodBuilderHome + | Home + | Stats | TextileSimulator Definition.Trigram (Maybe TextileQuery.Query) | TextileSimulatorExample Uuid - | Stats + | TextileSimulatorHome parser : Parser (Route -> a) a @@ -172,9 +172,6 @@ toString route = let pieces = case route of - Home -> - [] - Api -> [ "api" ] @@ -202,9 +199,6 @@ toString route = Explore scope dataset -> "explore" :: Scope.toString scope :: Dataset.toRoutePath dataset - FoodBuilderHome -> - [ "food" ] - FoodBuilder trigram Nothing -> [ "food", Definition.toString trigram ] @@ -214,8 +208,14 @@ toString route = FoodBuilderExample uuid -> [ "food", "edit-example", Uuid.toString uuid ] - TextileSimulatorHome -> - [ "textile", "simulator" ] + FoodBuilderHome -> + [ "food" ] + + Home -> + [] + + Stats -> + [ "stats" ] TextileSimulator trigram (Just query) -> [ "textile" @@ -233,7 +233,7 @@ toString route = TextileSimulatorExample uuid -> [ "textile", "edit-example", Uuid.toString uuid ] - Stats -> - [ "stats" ] + TextileSimulatorHome -> + [ "textile", "simulator" ] in "#/" ++ String.join "/" pieces diff --git a/src/Server.elm b/src/Server.elm index 1fa68104f..8de961528 100644 --- a/src/Server.elm +++ b/src/Server.elm @@ -47,7 +47,7 @@ apiDocUrl = sendResponse : Int -> Request -> Encode.Value -> Cmd Msg -sendResponse httpStatus { method, url, jsResponseHandler } body = +sendResponse httpStatus { jsResponseHandler, method, url } body = Encode.object [ ( "status", Encode.int httpStatus ) , ( "method", Encode.string method ) @@ -69,15 +69,15 @@ encodeStringError error = toResponse : Result String Encode.Value -> JsonResponse toResponse encodedResult = case encodedResult of - Ok encoded -> - ( 200, encoded ) - Err error -> ( 400, encodeStringError error ) + Ok encoded -> + ( 200, encoded ) + toAllImpactsSimple : Simulator -> Encode.Value -toAllImpactsSimple { inputs, impacts } = +toAllImpactsSimple { impacts, inputs } = Encode.object [ ( "webUrl", serverRootUrl ++ toTextileWebUrl Nothing inputs |> Encode.string ) , ( "impacts", Impact.encode impacts ) @@ -101,7 +101,7 @@ toTextileWebUrl maybeTrigram textileQuery = toSingleImpactSimple : Definition.Trigram -> Simulator -> Encode.Value -toSingleImpactSimple trigram { inputs, impacts } = +toSingleImpactSimple trigram { impacts, inputs } = Encode.object [ ( "webUrl", serverRootUrl ++ toTextileWebUrl (Just trigram) inputs |> Encode.string ) , ( "impacts" @@ -205,96 +205,96 @@ handleRequest : Db -> Request -> JsonResponse handleRequest db request = case Route.endpoint db request of -- GET routes - Just Route.GetFoodCountryList -> + Just Route.FoodGetCountryList -> db.countries |> Scope.only Scope.Food |> Encode.list encodeCountry |> respondWith 200 - Just Route.GetFoodIngredientList -> + Just Route.FoodGetIngredientList -> db.food.ingredients |> encodeIngredients |> respondWith 200 - Just Route.GetFoodPackagingList -> + Just Route.FoodGetPackagingList -> db.food.processes |> List.filter (.category >> (==) FoodProcess.Packaging) |> encodeFoodProcessList |> respondWith 200 - Just Route.GetFoodTransformList -> + Just Route.FoodGetTransformList -> db.food.processes |> List.filter (.category >> (==) FoodProcess.Transform) |> encodeFoodProcessList |> respondWith 200 - Just (Route.GetFoodRecipe (Ok query)) -> + Just (Route.FoodGetRecipe (Ok query)) -> query |> executeFoodQuery db (toFoodResults query) - Just (Route.GetFoodRecipe (Err errors)) -> + Just (Route.FoodGetRecipe (Err errors)) -> Query.encodeErrors errors |> respondWith 400 - Just Route.GetTextileCountryList -> + Just Route.TextileGetCountryList -> db.countries |> Scope.only Scope.Textile |> Encode.list encodeCountry |> respondWith 200 - Just Route.GetTextileMaterialList -> + Just Route.TextileGetMaterialList -> db.textile.materials |> Encode.list encodeMaterial |> respondWith 200 - Just Route.GetTextileProductList -> + Just Route.TextileGetProductList -> db.textile.products |> Encode.list encodeProduct |> respondWith 200 - Just (Route.GetTextileSimulator (Ok query)) -> + Just (Route.TextileGetSimulator (Ok query)) -> query |> executeTextileQuery db toAllImpactsSimple - Just (Route.GetTextileSimulator (Err errors)) -> + Just (Route.TextileGetSimulator (Err errors)) -> Query.encodeErrors errors |> respondWith 400 - Just (Route.GetTextileSimulatorDetailed (Ok query)) -> + Just (Route.TextileGetSimulatorDetailed (Ok query)) -> query |> executeTextileQuery db Simulator.encode - Just (Route.GetTextileSimulatorDetailed (Err errors)) -> + Just (Route.TextileGetSimulatorDetailed (Err errors)) -> Query.encodeErrors errors |> respondWith 400 - Just (Route.GetTextileSimulatorSingle trigram (Ok query)) -> + Just (Route.TextileGetSimulatorSingle trigram (Ok query)) -> query |> executeTextileQuery db (toSingleImpactSimple trigram) - Just (Route.GetTextileSimulatorSingle _ (Err errors)) -> + Just (Route.TextileGetSimulatorSingle _ (Err errors)) -> Query.encodeErrors errors |> respondWith 400 -- POST routes - Just Route.PostFoodRecipe -> + Just Route.FoodPostRecipe -> request.body |> handleDecodeBody BuilderQuery.decode (\query -> executeFoodQuery db (toFoodResults query) query ) - Just Route.PostTextileSimulator -> + Just Route.TextilePostSimulator -> request.body |> handleDecodeBody TextileQuery.decode (executeTextileQuery db toAllImpactsSimple) - Just Route.PostTextileSimulatorDetailed -> + Just Route.TextilePostSimulatorDetailed -> request.body |> handleDecodeBody TextileQuery.decode (executeTextileQuery db Simulator.encode) - Just (Route.PostTextileSimulatorSingle trigram) -> + Just (Route.TextilePostSimulatorSingle trigram) -> request.body |> handleDecodeBody TextileQuery.decode (executeTextileQuery db (toSingleImpactSimple trigram)) @@ -307,33 +307,32 @@ handleRequest db request = handleDecodeBody : Decode.Decoder a -> (a -> JsonResponse) -> Encode.Value -> JsonResponse handleDecodeBody decoder mapper jsonBody = case Decode.decodeValue decoder jsonBody of - Ok x -> - mapper x - Err error -> ( 400, Encode.string (Decode.errorToString error) ) + Ok x -> + mapper x + update : Msg -> Cmd Msg update msg = case msg of Received request -> case db request.processes of - Ok db -> - cmdRequest db request - Err error -> encodeStringError error |> sendResponse 503 request + Ok db -> + cmdRequest db request + main : Program () () Msg main = + -- Note: The Api server being stateless, there's no need for a model Platform.worker { init = always ( (), Cmd.none ) - - -- The Api server being stateless, there's no need of a model - , update = \msg _ -> ( (), update msg ) , subscriptions = always (input Received) + , update = \msg _ -> ( (), update msg ) } diff --git a/src/Server/Query.elm b/src/Server/Query.elm index 0ffc90685..393c61677 100644 --- a/src/Server/Query.elm +++ b/src/Server/Query.elm @@ -66,11 +66,11 @@ succeed = parseFoodQuery : List Country -> Food.Db -> Parser (Result Errors BuilderQuery.Query) parseFoodQuery countries food = succeed (Ok BuilderQuery.Query) + |> apply (distributionParser "distribution") |> apply (ingredientListParser "ingredients" countries food) - |> apply (maybeTransformParser "transform" food.processes) |> apply (packagingListParser "packaging" food.processes) - |> apply (distributionParser "distribution") |> apply (preparationListParser "preparation") + |> apply (maybeTransformParser "transform" food.processes) ingredientListParser : String -> List Country -> Food.Db -> Parser (ParseResult (List BuilderQuery.IngredientQuery)) @@ -98,9 +98,9 @@ ingredientParser countries food string = |> Ingredient.findByID (Ingredient.idFromString id) in Ok BuilderQuery.IngredientQuery + |> RE.andMap (Ok Nothing) |> RE.andMap (Result.map .id ingredient) |> RE.andMap (validateMassInGrams mass) - |> RE.andMap (Ok Nothing) |> RE.andMap (Result.map Ingredient.byPlaneByDefault ingredient) [ id, mass, countryCode ] -> @@ -110,9 +110,9 @@ ingredientParser countries food string = |> Ingredient.findByID (Ingredient.idFromString id) in Ok BuilderQuery.IngredientQuery + |> RE.andMap (countryParser countries Scope.Food countryCode) |> RE.andMap (Result.map .id ingredient) |> RE.andMap (validateMassInGrams mass) - |> RE.andMap (countryParser countries Scope.Food countryCode) |> RE.andMap (Result.map Ingredient.byPlaneByDefault ingredient) [ id, mass, countryCode, byPlane ] -> @@ -122,9 +122,9 @@ ingredientParser countries food string = |> Ingredient.findByID (Ingredient.idFromString id) in Ok BuilderQuery.IngredientQuery + |> RE.andMap (countryParser countries Scope.Food countryCode) |> RE.andMap (Result.map .id ingredient) |> RE.andMap (validateMassInGrams mass) - |> RE.andMap (countryParser countries Scope.Food countryCode) |> RE.andMap (ingredient |> Result.andThen (byPlaneParser byPlane)) [ "" ] -> @@ -257,6 +257,35 @@ validateMassInGrams string = |> Result.map Mass.grams +validatePhysicalDurability : String -> Result String Unit.PhysicalDurability +validatePhysicalDurability string = + string + |> String.toFloat + |> Result.fromMaybe ("Durabilité invalide\u{202F}: " ++ string) + |> Result.andThen + (\durability -> + let + minFloatDurability = + Unit.minDurability Unit.PhysicalDurability |> Unit.physicalDurabilityToFloat + + maxFloatDurability = + Unit.maxDurability Unit.PhysicalDurability |> Unit.physicalDurabilityToFloat + in + if durability < minFloatDurability || durability > maxFloatDurability then + Err + ("La durabilité doit être comprise entre " + ++ String.fromFloat minFloatDurability + ++ " et " + ++ String.fromFloat maxFloatDurability + ++ "." + ) + + else + Ok durability + ) + |> Result.map Unit.PhysicalDurability + + maybeTransformParser : String -> List FoodProcess.Process -> Parser (ParseResult (Maybe BuilderQuery.ProcessQuery)) maybeTransformParser key transforms = Query.string key @@ -286,6 +315,20 @@ maybePriceParser key = ) +maybePhysicalDurabilityParser : String -> Parser (ParseResult (Maybe Unit.PhysicalDurability)) +maybePhysicalDurabilityParser key = + Query.string key + |> Query.map + (Maybe.map + (\durability -> + validatePhysicalDurability durability + |> Result.map Just + |> Result.mapError (\err -> ( key, err )) + ) + >> Maybe.withDefault (Ok Nothing) + ) + + distributionParser : String -> Parser (ParseResult (Maybe Distribution)) distributionParser key = Query.string key @@ -344,29 +387,30 @@ parseTransform_ transforms string = parseTextileQuery : List Country -> Textile.Db -> Parser (Result Errors TextileQuery.Query) parseTextileQuery countries textile = succeed (Ok TextileQuery.Query) - |> apply (massParserInKilograms "mass") - |> apply (materialListParser "materials" textile.materials countries) - |> apply (productParser "product" textile.products) - |> apply (maybeTextileCountryParser "countrySpinning" countries) - |> apply (maybeTextileCountryParser "countryFabric" countries) + |> apply (maybeSplitParser "airTransportRatio") + |> apply (maybeBusiness "business") |> apply (maybeTextileCountryParser "countryDyeing" countries) + |> apply (maybeTextileCountryParser "countryFabric" countries) |> apply (maybeTextileCountryParser "countryMaking" countries) - |> apply (maybeSplitParser "airTransportRatio") - |> apply (maybeMakingWasteParser "makingWaste") - |> apply (maybeMakingDeadStockParser "makingDeadStock") - |> apply (maybeMakingComplexityParser "makingComplexity") - |> apply (maybeYarnSizeParser "yarnSize") - |> apply (maybeSurfaceMassParser "surfaceMass") - |> apply (maybeFabricParser "fabricProcess") + |> apply (maybeTextileCountryParser "countrySpinning" countries) |> apply (maybeDisabledStepsParser "disabledSteps") - |> apply (maybeBoolParser "fading") |> apply (maybeDyeingMedium "dyeingMedium") - |> apply (maybePrinting "printing") - |> apply (maybeBusiness "business") + |> apply (maybeFabricParser "fabricProcess") + |> apply (maybeBoolParser "fading") + |> apply (maybeMakingComplexityParser "makingComplexity") + |> apply (maybeMakingDeadStockParser "makingDeadStock") + |> apply (maybeMakingWasteParser "makingWaste") + |> apply (massParserInKilograms "mass") + |> apply (materialListParser "materials" textile.materials countries) |> apply (maybeIntParser "numberOfReferences") + |> apply (maybePhysicalDurabilityParser "physicalDurability") |> apply (maybePriceParser "price") + |> apply (maybePrinting "printing") + |> apply (productParser "product" textile.products) + |> apply (maybeSurfaceMassParser "surfaceMass") |> apply (maybeBoolParser "traceability") |> apply (boolParser { default = False } "upcycled") + |> apply (maybeYarnSizeParser "yarnSize") toErrors : ParseResult a -> Result Errors a @@ -467,10 +511,10 @@ parseMaterial_ materials countries string = |> Result.andThen (\material -> Ok TextileQuery.MaterialQuery + |> RE.andMap (countryParser countries Scope.Textile countryCode) |> RE.andMap (Ok material.id) |> RE.andMap (parseSplit share) |> RE.andMap (parseSpinning material spinningString) - |> RE.andMap (countryParser countries Scope.Textile countryCode) ) [ id, share, spinningString ] -> @@ -479,17 +523,18 @@ parseMaterial_ materials countries string = |> Result.andThen (\material -> Ok TextileQuery.MaterialQuery + |> RE.andMap (Ok Nothing) |> RE.andMap (Ok material.id) |> RE.andMap (parseSplit share) |> RE.andMap (parseSpinning material spinningString) - |> RE.andMap (Ok Nothing) ) [ id, share ] -> Ok TextileQuery.MaterialQuery + |> RE.andMap (Ok Nothing) |> RE.andMap (parseMaterialId_ materials id) |> RE.andMap (parseSplit share) - |> Result.map (\partiallyApplied -> partiallyApplied Nothing Nothing) + |> RE.andMap (Ok Nothing) [ "" ] -> Err <| "Format de matière vide." @@ -563,11 +608,11 @@ maybeDyeingMedium key = (Maybe.map (\str -> case DyeingMedium.fromString str of - Ok dyeingMedium -> - Ok (Just dyeingMedium) - Err err -> Err ( key, err ) + + Ok dyeingMedium -> + Ok (Just dyeingMedium) ) >> Maybe.withDefault (Ok Nothing) ) @@ -580,11 +625,11 @@ maybePrinting key = (Maybe.map (\str -> case Printing.fromStringParam str of - Ok printing -> - Ok (Just printing) - Err err -> Err ( key, err ) + + Ok printing -> + Ok (Just printing) ) >> Maybe.withDefault (Ok Nothing) ) @@ -613,11 +658,11 @@ maybeMakingComplexityParser key = (Maybe.map (\str -> case MakingComplexity.fromString str of - Ok printing -> - Ok (Just printing) - Err err -> Err ( key, err ) + + Ok printing -> + Ok (Just printing) ) >> Maybe.withDefault (Ok Nothing) ) @@ -778,11 +823,11 @@ maybeFabricParser key = (Maybe.map (\str -> case Fabric.fromString str of - Ok fabric -> - Ok (Just fabric) - Err err -> Err ( key, err ) + + Ok fabric -> + Ok (Just fabric) ) >> Maybe.withDefault (Ok Nothing) ) diff --git a/src/Server/Request.elm b/src/Server/Request.elm index 64b735532..e501f017e 100644 --- a/src/Server/Request.elm +++ b/src/Server/Request.elm @@ -10,9 +10,9 @@ type alias Request = -- - `url` is ExpressJS' request `url` string -- - `body` is the JSON body; if no JSON body exist in the request, fallbacks to `{}` -- - `jsResponseHandler` is an ExpressJS response callback function - { method : String - , url : String - , body : Encode.Value - , processes : StaticJson.RawJsonProcesses + { body : Encode.Value , jsResponseHandler : Encode.Value + , method : String + , processes : StaticJson.RawJsonProcesses + , url : String } diff --git a/src/Server/Route.elm b/src/Server/Route.elm index 4485f7799..34f519a64 100644 --- a/src/Server/Route.elm +++ b/src/Server/Route.elm @@ -27,63 +27,63 @@ type Route = -- Food Routes -- GET -- Food country list - GetFoodCountryList + FoodGetCountryList -- Food ingredient list - | GetFoodIngredientList + | FoodGetIngredientList -- Food packaging list - | GetFoodPackagingList - -- Food transforms list - | GetFoodTransformList + | FoodGetPackagingList -- Food recipe builder (GET, query string) - | GetFoodRecipe (Result Query.Errors BuilderQuery.Query) + | FoodGetRecipe (Result Query.Errors BuilderQuery.Query) + -- Food transforms list + | FoodGetTransformList -- POST -- Food recipe builder (POST, JSON body) - | PostFoodRecipe + | FoodPostRecipe -- -- Textile Routes -- GET -- Textile country list - | GetTextileCountryList + | TextileGetCountryList -- Textile Material list - | GetTextileMaterialList + | TextileGetMaterialList -- Textile Product list - | GetTextileProductList + | TextileGetProductList -- Textile Simple version of all impacts (GET, query string) - | GetTextileSimulator (Result Query.Errors TextileQuery.Query) + | TextileGetSimulator (Result Query.Errors TextileQuery.Query) -- Textile Detailed version for all impacts (GET, query string) - | GetTextileSimulatorDetailed (Result Query.Errors TextileQuery.Query) + | TextileGetSimulatorDetailed (Result Query.Errors TextileQuery.Query) -- Textile Simple version for one specific impact (GET, query string) - | GetTextileSimulatorSingle Definition.Trigram (Result Query.Errors TextileQuery.Query) + | TextileGetSimulatorSingle Definition.Trigram (Result Query.Errors TextileQuery.Query) -- POST -- Textile Simple version of all impacts (POST, JSON body) - | PostTextileSimulator + | TextilePostSimulator -- Textile Detailed version for all impacts (POST, JSON body) - | PostTextileSimulatorDetailed + | TextilePostSimulatorDetailed -- Textile Simple version for one specific impact (POST, JSON bosy) - | PostTextileSimulatorSingle Definition.Trigram + | TextilePostSimulatorSingle Definition.Trigram parser : Food.Db -> Textile.Db -> List Country -> Parser (Route -> a) a parser foodDb textile countries = Parser.oneOf [ -- Food - Parser.map GetFoodCountryList (s "GET" s "food" s "countries") - , Parser.map GetFoodIngredientList (s "GET" s "food" s "ingredients") - , Parser.map GetFoodTransformList (s "GET" s "food" s "transforms") - , Parser.map GetFoodPackagingList (s "GET" s "food" s "packagings") - , Parser.map GetFoodRecipe (s "GET" s "food" Query.parseFoodQuery countries foodDb) - , Parser.map PostFoodRecipe (s "POST" s "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") -- Textile - , Parser.map GetTextileCountryList (s "GET" s "textile" s "countries") - , Parser.map GetTextileMaterialList (s "GET" s "textile" s "materials") - , Parser.map GetTextileProductList (s "GET" s "textile" s "products") - , Parser.map GetTextileSimulator (s "GET" s "textile" s "simulator" Query.parseTextileQuery countries textile) - , Parser.map GetTextileSimulatorDetailed (s "GET" s "textile" s "simulator" s "detailed" Query.parseTextileQuery countries textile) - , Parser.map GetTextileSimulatorSingle (s "GET" s "textile" s "simulator" Impact.parseTrigram Query.parseTextileQuery countries textile) - , Parser.map PostTextileSimulator (s "POST" s "textile" s "simulator") - , Parser.map PostTextileSimulatorDetailed (s "POST" s "textile" s "simulator" s "detailed") - , Parser.map PostTextileSimulatorSingle (s "POST" s "textile" s "simulator" Impact.parseTrigram) + , 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) ] diff --git a/src/Static/Db.elm b/src/Static/Db.elm index f7c88b292..ae14df170 100644 --- a/src/Static/Db.elm +++ b/src/Static/Db.elm @@ -12,15 +12,16 @@ import Data.Textile.Db as TextileDb import Data.Transport exposing (Distances) import Json.Decode as Decode exposing (Decoder) import Json.Decode.Pipeline as JDP +import Result.Extra as RE import Static.Json as StaticJson exposing (RawJsonProcesses) type alias Db = - { definitions : Definitions - , textile : TextileDb.Db - , food : FoodDb.Db - , countries : List Country + { countries : List Country + , definitions : Definitions , distances : Distances + , food : FoodDb.Db + , textile : TextileDb.Db } @@ -29,13 +30,12 @@ db procs = StaticJson.db procs |> Result.andThen (\{ foodDb, textileDb } -> - Result.map3 - (\okImpactDefinitions okCountries okDistances -> - Db okImpactDefinitions textileDb foodDb okCountries okDistances - ) - impactDefinitions - (countries textileDb) - distances + Ok Db + |> RE.andMap (countries textileDb) + |> RE.andMap impactDefinitions + |> RE.andMap distances + |> RE.andMap (Ok foodDb) + |> RE.andMap (Ok textileDb) ) diff --git a/src/Views/Alert.elm b/src/Views/Alert.elm index a05df48ea..cd028cf12 100644 --- a/src/Views/Alert.elm +++ b/src/Views/Alert.elm @@ -15,10 +15,10 @@ import Views.Icon as Icon type alias Config msg = - { level : Level - , close : Maybe msg - , title : Maybe String + { close : Maybe msg , content : List (Html msg) + , level : Level + , title : Maybe String } @@ -44,9 +44,7 @@ icon level = httpError : Http.Error -> Html msg httpError error = simple - { title = Just "Erreur de chargement des données" - , close = Nothing - , level = Info + { close = Nothing , content = case error |> HttpCommon.errorToString |> String.lines of [] -> @@ -76,6 +74,8 @@ httpError error = [ text "Envoyer un rapport d'incident" ] ] ] + , level = Info + , title = Just "Erreur de chargement des données" } @@ -85,7 +85,7 @@ preformatted config = simple : Config msg -> Html msg -simple { level, content, title, close } = +simple { close, content, level, title } = div [ class <| "alert alert-" ++ levelToClass level , classList [ ( "alert-dismissible", close /= Nothing ) ] diff --git a/src/Views/AutocompleteSelector.elm b/src/Views/AutocompleteSelector.elm index 76f77aa09..48d2fce53 100644 --- a/src/Views/AutocompleteSelector.elm +++ b/src/Views/AutocompleteSelector.elm @@ -16,29 +16,24 @@ type alias Config element msg = , onAutocompleteSelect : msg , placeholderText : String , title : String - , toLabel : element -> String , toCategory : element -> String + , toLabel : element -> String } view : Config element msg -> Html msg view ({ autocompleteState, closeModal, footer, noOp, onAutocomplete, onAutocompleteSelect, placeholderText, title } as config) = ModalView.view - { size = ModalView.Large - , close = closeModal - , noOp = noOp - , title = title - , subTitle = Nothing - , formAction = Nothing + { close = closeModal , content = let - { query, choices, selectedIndex } = + { choices, query, selectedIndex } = Autocomplete.viewState autocompleteState - { inputEvents, choiceEvents } = + { choiceEvents, inputEvents } = AutocompleteView.events - { onSelect = onAutocompleteSelect - , mapHtml = onAutocomplete + { mapHtml = onAutocomplete + , onSelect = onAutocompleteSelect } in [ input @@ -60,11 +55,16 @@ view ({ autocompleteState, closeModal, footer, noOp, onAutocomplete, onAutocompl |> div [ class "ElementAutocomplete", id "element-autocomplete-choices" ] ] , footer = footer + , formAction = Nothing + , noOp = noOp + , size = ModalView.Large + , subTitle = Nothing + , title = title } renderChoice : Config element msg -> (Int -> List (Attribute msg)) -> Maybe Int -> Int -> element -> Html msg -renderChoice { toLabel, toCategory } events selectedIndex_ index element = +renderChoice { toCategory, toLabel } events selectedIndex_ index element = let selected = Autocomplete.isSelected selectedIndex_ index diff --git a/src/Views/BaseElement.elm b/src/Views/BaseElement.elm index 4292598aa..f75ad6059 100644 --- a/src/Views/BaseElement.elm +++ b/src/Views/BaseElement.elm @@ -13,16 +13,16 @@ import Views.Icon as Icon type alias BaseElement element quantity = - { element : element + { country : Maybe Country + , element : element , quantity : quantity - , country : Maybe Country } type alias Db element = - { elements : List element - , countries : List Country + { countries : List Country , definitions : Definitions + , elements : List element } @@ -34,12 +34,10 @@ type alias Config element quantity msg = , delete : element -> msg , excluded : List element , impact : Impacts - - -- TODO: introduce complementsView , openExplorerDetails : element -> msg , quantityView : { quantity : quantity, onChange : Maybe quantity -> msg } -> Html msg - , selectedImpact : Definition , selectElement : element -> Autocomplete element -> msg + , selectedImpact : Definition , toId : element -> String , toString : element -> String , toTooltip : element -> String @@ -64,8 +62,7 @@ view ({ baseElement, db, impact } as config) = in [ span [ class "QuantityInputWrapper" ] [ config.quantityView - { quantity = baseElement.quantity - , onChange = + { onChange = \maybeQuantity -> case maybeQuantity of Just quantity -> @@ -73,6 +70,7 @@ view ({ baseElement, db, impact } as config) = _ -> updateEvent baseElement + , quantity = baseElement.quantity } ] , autocompleteState @@ -133,7 +131,7 @@ deleteItemButton { disabled } event = selectorView : Config element quantity msg -> msg -> Html msg -selectorView { baseElement, openExplorerDetails, toId, toTooltip, toString } selectElement = +selectorView { baseElement, openExplorerDetails, toId, toString, toTooltip } selectElement = let { element } = baseElement diff --git a/src/Views/Bookmark.elm b/src/Views/Bookmark.elm index 33aa2c2bd..697ba5691 100644 --- a/src/Views/Bookmark.elm +++ b/src/Views/Bookmark.elm @@ -16,19 +16,17 @@ import Views.Icon as Icon type alias ManagerConfig msg = - { session : Session - , activeTab : ActiveTab + { activeTab : ActiveTab , bookmarkName : String - , impact : Definition - , scope : Scope - - -- Messages - , copyToClipBoard : String -> msg , compare : msg + , copyToClipBoard : String -> msg , delete : Bookmark -> msg + , impact : Definition , save : msg - , update : String -> msg + , scope : Scope + , session : Session , switchTab : ActiveTab -> msg + , update : String -> msg } @@ -41,30 +39,30 @@ view : ManagerConfig msg -> Html msg view cfg = CardTabs.view { attrs = [] + , content = + [ case cfg.activeTab of + SaveTab -> + managerView cfg + + ShareTab -> + shareTabView cfg + ] , tabs = [ ( SaveTab, text "Sauvegarder" ) , ( ShareTab, text "Partager" ) ] |> List.map (\( tab, label ) -> - { label = label + { active = cfg.activeTab == tab + , label = label , onTabClick = cfg.switchTab tab - , active = cfg.activeTab == tab } ) - , content = - [ case cfg.activeTab of - ShareTab -> - shareTabView cfg - - SaveTab -> - managerView cfg - ] } shareTabView : ManagerConfig msg -> Html msg -shareTabView { session, impact, copyToClipBoard, scope } = +shareTabView { copyToClipBoard, impact, scope, session } = let ( shareableLink, apiCall, jsonParams ) = case scope of diff --git a/src/Views/CardTabs.elm b/src/Views/CardTabs.elm index ef317fc78..3ae451c0c 100644 --- a/src/Views/CardTabs.elm +++ b/src/Views/CardTabs.elm @@ -6,26 +6,26 @@ import Html.Events exposing (..) type alias Tab msg = - { label : Html msg - , active : Bool + { active : Bool + , label : Html msg , onTabClick : msg } type alias Config msg = { attrs : List (Attribute msg) - , tabs : List (Tab msg) , content : List (Html msg) + , tabs : List (Tab msg) } view : Config msg -> Html msg -view { attrs, tabs, content } = +view { attrs, content, tabs } = div [ class "CardTabs card shadow-sm" ] (div (class "card-header px-0 pb-0 border-bottom-0 bg-white" :: attrs) [ tabs |> List.map - (\{ label, onTabClick, active } -> + (\{ active, label, onTabClick } -> li [ class "TabsTab nav-item", classList [ ( "active", active ) ] ] [ button [ class "nav-link no-outline border-top-0" diff --git a/src/Views/Comparator.elm b/src/Views/Comparator.elm index f74123593..9f61a0c1f 100644 --- a/src/Views/Comparator.elm +++ b/src/Views/Comparator.elm @@ -40,9 +40,9 @@ type ComparisonType type alias ChartsData = - { label : String + { complementsImpact : Impact.ComplementsImpacts , impacts : Impact.Impacts - , complementsImpact : Impact.ComplementsImpacts + , label : String , stepsImpacts : Impact.StepsImpacts } @@ -60,7 +60,7 @@ view config = sidebarView : Config msg -> List (Html msg) -sidebarView { session, toggle, selectAll, selectNone } = +sidebarView { selectAll, selectNone, session, toggle } = [ div [ class "p-2 ps-3 mb-0 text-muted" ] [ text "Sélectionnez des simulations pour les comparer" ] @@ -112,9 +112,9 @@ addToComparison session label query = |> Recipe.compute session.db |> Result.map (\( _, { recipe, total } as results ) -> - { label = label + { complementsImpact = recipe.totalComplementsImpact , impacts = total - , complementsImpact = recipe.totalComplementsImpact + , label = label , stepsImpacts = results |> Recipe.toStepsImpacts Definition.Ecs @@ -126,9 +126,9 @@ addToComparison session label query = |> Simulator.compute session.db |> Result.map (\simulator -> - { label = label + { complementsImpact = simulator.complementsImpacts , impacts = simulator.impacts - , complementsImpact = simulator.complementsImpacts + , label = label , stepsImpacts = simulator |> Simulator.toStepsImpacts Definition.Ecs @@ -176,6 +176,14 @@ comparatorView config = ) |> ul [ class "Tabs nav nav-tabs nav-fill justify-content-end gap-3 mt-2 px-2" ] , case charts of + Err error -> + Alert.simple + { close = Nothing + , content = [ text error ] + , level = Alert.Danger + , title = Just "Erreur" + } + Ok [] -> p [ class "d-flex h-100 justify-content-center align-items-center pb-5" ] [ text "Sélectionnez une ou plusieurs simulations pour les comparer" ] @@ -187,12 +195,12 @@ comparatorView config = IndividualImpacts -> dataForIndividualImpacts config.session.db.definitions chartsData - Subscores -> - dataForSubscoresImpacts config.session.db.definitions chartsData - Steps -> dataForSteps chartsData + Subscores -> + dataForSubscoresImpacts config.session.db.definitions chartsData + Total -> dataForTotalImpacts chartsData in @@ -203,12 +211,12 @@ comparatorView config = IndividualImpacts -> "individual-impacts" - Subscores -> - "grouped-impacts" - Steps -> "steps-impacts" + Subscores -> + "grouped-impacts" + Total -> "total-impacts" ) @@ -217,14 +225,6 @@ comparatorView config = [ attribute "data" data ] [] ] - - Err error -> - Alert.simple - { level = Alert.Danger - , close = Nothing - , title = Just "Erreur" - , content = [ text error ] - } ] @@ -277,7 +277,7 @@ dataForIndividualImpacts definitions chartsData = in chartsData |> List.map - (\{ label, impacts, complementsImpact } -> + (\{ complementsImpact, impacts, label } -> let complementImpacts = Impact.complementsImpactAsChartEntries complementsImpact @@ -305,7 +305,7 @@ dataForSubscoresImpacts : Definitions -> List ChartsData -> String dataForSubscoresImpacts definitions chartsData = chartsData |> List.map - (\{ label, impacts, complementsImpact } -> + (\{ complementsImpact, impacts, label } -> let complementImpacts = Impact.totalComplementsImpactAsChartEntry complementsImpact @@ -313,13 +313,13 @@ dataForSubscoresImpacts definitions chartsData = entries = impacts |> Impact.toProtectionAreas definitions - |> (\{ climate, biodiversity, health, resources } -> + |> (\{ biodiversity, climate, health, resources } -> List.reverse [ complementImpacts - , { name = "Climat", color = "#9025be", value = Unit.impactToFloat climate } - , { name = "Biodiversité", color = "#00b050", value = Unit.impactToFloat biodiversity } - , { name = "Santé environnementale", color = "#ffc000", value = Unit.impactToFloat health } - , { name = "Ressource", color = "#0070c0", value = Unit.impactToFloat resources } + , { color = "#9025be", name = "Climat", value = Unit.impactToFloat climate } + , { color = "#00b050", name = "Biodiversité", value = Unit.impactToFloat biodiversity } + , { color = "#ffc000", name = "Santé environnementale", value = Unit.impactToFloat health } + , { color = "#0070c0", name = "Ressource", value = Unit.impactToFloat resources } ] ) in @@ -355,13 +355,13 @@ dataForTotalImpacts : List ChartsData -> String dataForTotalImpacts chartsData = chartsData |> List.map - (\{ label, impacts } -> + (\{ impacts, label } -> Encode.object [ ( "label", Encode.string label ) , ( "data" , Encode.list Impact.encodeAggregatedScoreChartEntry - [ { name = "Impact total" - , color = "#333333" + [ { color = "#333333" + , name = "Impact total" , value = impacts |> Impact.getImpact Definition.Ecs diff --git a/src/Views/Component/SplitInput.elm b/src/Views/Component/SplitInput.elm index f8ac5d716..2fd32c4c6 100644 --- a/src/Views/Component/SplitInput.elm +++ b/src/Views/Component/SplitInput.elm @@ -8,13 +8,13 @@ import Html.Events exposing (..) type alias Config msg = { disabled : Bool - , share : Split , onChange : Maybe Split -> msg + , share : Split } view : Config msg -> Html msg -view { disabled, share, onChange } = +view { disabled, onChange, share } = div [ class "input-group" ] [ input [ class "form-control text-end incdec-arrows-left" diff --git a/src/Views/Format.elm b/src/Views/Format.elm index 010e5ecc9..b3f7f8655 100644 --- a/src/Views/Format.elm +++ b/src/Views/Format.elm @@ -44,13 +44,13 @@ import Quantity import Volume exposing (Volume) -formatImpactFloat : { a | unit : String, decimals : Int } -> Float -> Html msg -formatImpactFloat { unit, decimals } = +formatImpactFloat : { a | decimals : Int, unit : String } -> Float -> Html msg +formatImpactFloat { decimals, unit } = formatRichFloat decimals unit formatImpact : Definition -> Impacts -> Html msg -formatImpact { trigram, unit, decimals } = +formatImpact { decimals, trigram, unit } = Impact.getImpact trigram >> Unit.impactToFloat >> formatRichFloat decimals unit diff --git a/src/Views/Icon.elm b/src/Views/Icon.elm index ec93487b9..04d9385a8 100644 --- a/src/Views/Icon.elm +++ b/src/Views/Icon.elm @@ -46,11 +46,6 @@ clipboard = icon "clipboard" -exclamation : Html msg -exclamation = - icon "exclamation" - - ham : Html msg ham = icon "ham" diff --git a/src/Views/ImpactTabs.elm b/src/Views/ImpactTabs.elm index acedf6c54..85a05cde5 100644 --- a/src/Views/ImpactTabs.elm +++ b/src/Views/ImpactTabs.elm @@ -34,55 +34,38 @@ type alias Config msg = , impactDefinition : Definition , onStepClick : String -> msg , scoring : Scoring + , session : Session , stepsImpacts : Impact.StepsImpacts , switchImpactsTab : Tab -> msg , total : Impacts - , session : Session } view : Definitions -> Config msg -> Html msg -view definitions { activeImpactsTab, impactDefinition, switchImpactsTab, total, complementsImpact, onStepClick, scoring, stepsImpacts, session } = +view definitions { activeImpactsTab, complementsImpact, impactDefinition, onStepClick, scoring, session, stepsImpacts, switchImpactsTab, total } = CardTabs.view { attrs = [] - , tabs = - (if impactDefinition.trigram == Definition.Ecs && Session.isAuthenticated session then - [ ( StepImpactsTab, text "Étapes" ) - , ( SubscoresTab, text "Sous-scores" ) - , ( DetailedImpactsTab, text "Impacts" ) - ] - - else - [ ( StepImpactsTab, text "Étapes" ) ] - ) - |> List.map - (\( tab, label ) -> - { label = label - , onTabClick = switchImpactsTab tab - , active = activeImpactsTab == tab - } - ) , content = [ case activeImpactsTab of DetailedImpactsTab -> total |> Impact.getAggregatedScoreData definitions .ecoscoreData - |> List.map (\{ name, value } -> { name = name, value = value, entryAttributes = [] }) + |> List.map (\{ name, value } -> { entryAttributes = [], name = name, value = value }) |> (++) [ -- Food ecosystemic services - { name = "Services écosystémiques" + { entryAttributes = [] + , name = "Services écosystémiques" , value = -(Unit.impactToFloat (Impact.sumEcosystemicImpacts complementsImpact)) - , entryAttributes = [] } -- Textile complements - , { name = "Complément export hors-Europe" + , { entryAttributes = [] + , name = "Complément export hors-Europe" , value = -(Unit.impactToFloat complementsImpact.outOfEuropeEOL) - , entryAttributes = [] } - , { name = "Complément microfibres" + , { entryAttributes = [] + , name = "Complément microfibres" , value = -(Unit.impactToFloat complementsImpact.microfibers) - , entryAttributes = [] } ] |> List.sortBy .value @@ -90,77 +73,94 @@ view definitions { activeImpactsTab, impactDefinition, switchImpactsTab, total, |> Table.percentageTable impactDefinition StepImpactsTab -> - [ { name = "Matières premières" - , value = stepsImpacts.materials - , entryAttributes = + [ { entryAttributes = [ StepsBorder.style Impact.stepsColors.materials , onClick <| onStepClick "materials-step" ] + , name = "Matières premières" + , value = stepsImpacts.materials } - , { name = "Transformation" - , value = stepsImpacts.transform - , entryAttributes = + , { entryAttributes = [ StepsBorder.style Impact.stepsColors.transform , onClick <| onStepClick "transform-step" ] + , name = "Transformation" + , value = stepsImpacts.transform } - , { name = "Emballage" - , value = stepsImpacts.packaging - , entryAttributes = + , { entryAttributes = [ StepsBorder.style Impact.stepsColors.packaging , onClick <| onStepClick "packaging-step" ] + , name = "Emballage" + , value = stepsImpacts.packaging } - , { name = "Transports" - , value = stepsImpacts.transports - , entryAttributes = + , { entryAttributes = [ StepsBorder.style Impact.stepsColors.transports , onClick <| onStepClick "transport-step" ] + , name = "Transports" + , value = stepsImpacts.transports } - , { name = "Distribution" - , value = stepsImpacts.distribution - , entryAttributes = + , { entryAttributes = [ StepsBorder.style Impact.stepsColors.distribution , onClick <| onStepClick "distribution-step" ] + , name = "Distribution" + , value = stepsImpacts.distribution } - , { name = "Utilisation" - , value = stepsImpacts.usage - , entryAttributes = + , { entryAttributes = [ StepsBorder.style Impact.stepsColors.usage , onClick <| onStepClick "usage-step" ] + , name = "Utilisation" + , value = stepsImpacts.usage } - , { name = "Fin de vie" - , value = stepsImpacts.endOfLife - , entryAttributes = + , { entryAttributes = [ StepsBorder.style Impact.stepsColors.endOfLife , onClick <| onStepClick "end-of-life-step" ] + , name = "Fin de vie" + , value = stepsImpacts.endOfLife } ] |> List.map - (\{ name, value, entryAttributes } -> - { name = name + (\{ entryAttributes, name, value } -> + { entryAttributes = style "cursor" "pointer" :: entryAttributes + , name = name , value = value |> Maybe.map Unit.impactToFloat |> Maybe.withDefault 0 - , entryAttributes = style "cursor" "pointer" :: entryAttributes } ) |> Table.percentageTable impactDefinition SubscoresTab -> Table.percentageTable impactDefinition - [ { name = "Climat", value = Unit.impactToFloat scoring.climate, entryAttributes = [] } - , { name = "Biodiversité", value = Unit.impactToFloat scoring.biodiversity, entryAttributes = [] } - , { name = "Santé environnementale", value = Unit.impactToFloat scoring.health, entryAttributes = [] } - , { name = "Ressource", value = Unit.impactToFloat scoring.resources, entryAttributes = [] } - , { name = "Compléments", value = -(Unit.impactToFloat scoring.complements), entryAttributes = [] } + [ { entryAttributes = [], name = "Climat", value = Unit.impactToFloat scoring.climate } + , { entryAttributes = [], name = "Biodiversité", value = Unit.impactToFloat scoring.biodiversity } + , { entryAttributes = [], name = "Santé environnementale", value = Unit.impactToFloat scoring.health } + , { entryAttributes = [], name = "Ressource", value = Unit.impactToFloat scoring.resources } + , { entryAttributes = [], name = "Compléments", value = -(Unit.impactToFloat scoring.complements) } ] ] + , tabs = + (if impactDefinition.trigram == Definition.Ecs && Session.isAuthenticated session then + [ ( StepImpactsTab, text "Étapes" ) + , ( SubscoresTab, text "Sous-scores" ) + , ( DetailedImpactsTab, text "Impacts" ) + ] + + else + [ ( StepImpactsTab, text "Étapes" ) ] + ) + |> List.map + (\( tab, label ) -> + { active = activeImpactsTab == tab + , label = label + , onTabClick = switchImpactsTab tab + } + ) } @@ -171,20 +171,20 @@ createConfig session impactDefinition activeImpactsTab onStepClick switchImpacts , impactDefinition = impactDefinition , onStepClick = onStepClick , scoring = Scoring.empty + , session = session , stepsImpacts = Impact.noStepsImpacts , switchImpactsTab = switchImpactsTab , total = Impact.empty - , session = session } forFood : Recipe.Results -> Config msg -> Config msg forFood results config = { config - | total = results.total - , complementsImpact = results.recipe.totalComplementsImpact + | complementsImpact = results.recipe.totalComplementsImpact , scoring = results.scoring , stepsImpacts = Recipe.toStepsImpacts config.impactDefinition.trigram results + , total = results.total } @@ -195,12 +195,12 @@ forTextile definitions simulator config = Simulator.getTotalImpactsWithoutComplements simulator in { config - | total = totalImpactsWithoutComplements - , complementsImpact = simulator.complementsImpacts + | complementsImpact = simulator.complementsImpacts , scoring = totalImpactsWithoutComplements |> Scoring.compute definitions (Impact.getTotalComplementsImpacts simulator.complementsImpacts) , stepsImpacts = simulator |> Simulator.toStepsImpacts config.impactDefinition.trigram + , total = totalImpactsWithoutComplements } diff --git a/src/Views/Markdown.elm b/src/Views/Markdown.elm index a8a0239d1..3b1d84434 100644 --- a/src/Views/Markdown.elm +++ b/src/Views/Markdown.elm @@ -1,28 +1,13 @@ module Views.Markdown exposing - ( ContentType(..) - , parse + ( parse , simple ) -import Data.Gitbook as Gitbook import Html exposing (..) -import Html.Attributes as Attr exposing (..) -import Markdown.Html as MdHtml +import Html.Attributes exposing (..) import Markdown.Parser as Parser -import Markdown.Renderer exposing (Renderer, defaultHtmlRenderer) +import Markdown.Renderer exposing (defaultHtmlRenderer) import Views.Alert as Alert -import Views.Icon as Icon -import Views.Link as Link - - -type ContentType - = Simple String - | Gitbook Gitbook.Page - - -siteUrl : String -siteUrl = - "https://ecobalyse.beta.gouv.fr" clean : String -> String @@ -30,134 +15,24 @@ clean = String.split "\n\n" >> List.map String.trim >> String.join "\n\n" -renderer : Maybe Gitbook.Path -> Renderer (Html msg) -renderer maybePath = - { defaultHtmlRenderer - | link = renderLink maybePath - , image = renderImage - , html = - MdHtml.oneOf - [ MdHtml.tag "hint" renderHint - |> MdHtml.withAttribute "level" - , MdHtml.tag "mark" renderMark - |> MdHtml.withAttribute "style" - - -- NOTE: sometimes gitbook exposes raw HTML in markdown - , MdHtml.tag "a" - (\href title -> renderLink maybePath { title = title, destination = href }) - |> MdHtml.withAttribute "href" - |> MdHtml.withOptionalAttribute "title" - , MdHtml.tag "code" (code []) - , MdHtml.tag "em" (em []) - , MdHtml.tag "p" (p [ class "mb-1" ]) - ] - } - - -renderHint : String -> List (Html msg) -> Html msg -renderHint level content = - let - makeIcon icon = - span [ class "fs-4", style "opacity" ".8", style "line-height" "0" ] [ icon ] - in - div - [ class <| "d-flex justify-content-between align-items-start gap-2 alert alert-" ++ level - ] - [ case level of - "danger" -> - makeIcon Icon.exclamation - - "info" -> - makeIcon Icon.info - - "warning" -> - makeIcon Icon.warning - - _ -> - text "" - , div [ class "flex-fill" ] content - ] - - -renderMark : String -> List (Html msg) -> Html msg -renderMark style_ = - span - [ class "mark" - , attribute "style" style_ - , style "background-color" "transparent" - ] - - -renderLink : Maybe Gitbook.Path -> { title : Maybe String, destination : String } -> List (Html msg) -> Html msg -renderLink maybePath { title, destination } = - let - destination_ = - Gitbook.handleMarkdownGitbookLink maybePath destination - - baseAttrs = - List.filterMap identity - [ Maybe.map Attr.title title - ] - in - if String.startsWith siteUrl destination_ then - Link.internal (Attr.href (String.replace siteUrl "" destination_) :: baseAttrs) - - else if String.startsWith "http" destination_ then - Link.external (Attr.href destination_ :: baseAttrs) - - else - Link.internal (Attr.href destination_ :: baseAttrs) - - -renderImage : { title : Maybe String, alt : String, src : String } -> Html msg -renderImage { title, src, alt } = - Html.img - (List.filterMap identity - [ Maybe.map Attr.title title - , src - |> String.replace "../.gitbook/assets/" - "https://raw.githubusercontent.com/MTES-MCT/ecobalyse/docs/.gitbook/assets/" - |> Attr.src - |> Just - , Just <| Attr.alt alt - , Just <| attribute "crossorigin" "anonymous" - ] - ) - [] - - simple : List (Attribute msg) -> String -> Html msg -simple attrs markdown = - view attrs (Simple markdown) - - -view : List (Attribute msg) -> ContentType -> Html msg -view attrs content = +simple attrs content = case parse content of - Ok rendered -> - div (class "Markdown bottomed-paragraphs" :: attrs) rendered - Err errors -> Alert.preformatted - { title = Just "Des erreurs ont été rencontrées" - , close = Nothing - , level = Alert.Danger + { close = Nothing , content = [ text errors ] + , level = Alert.Danger + , title = Just "Des erreurs ont été rencontrées" } + Ok rendered -> + div (class "Markdown bottomed-paragraphs" :: attrs) rendered -parse : ContentType -> Result String (List (Html msg)) -parse content = - let - ( markdown, path ) = - case content of - Simple string -> - ( string, Nothing ) - Gitbook page -> - ( page.markdown, Just page.path ) - in - clean markdown - |> Parser.parse - |> Result.mapError (List.map Parser.deadEndToString >> String.join "\n") - |> Result.andThen (Markdown.Renderer.render (renderer path)) +parse : String -> Result String (List (Html msg)) +parse = + clean + >> Parser.parse + >> Result.mapError (List.map Parser.deadEndToString >> String.join "\n") + >> Result.andThen (Markdown.Renderer.render defaultHtmlRenderer) diff --git a/src/Views/Modal.elm b/src/Views/Modal.elm index 04dc243a0..5f9153b95 100644 --- a/src/Views/Modal.elm +++ b/src/Views/Modal.elm @@ -7,14 +7,14 @@ import Json.Decode as Decode type alias Config msg = - { size : Size - , close : msg - , noOp : msg - , title : String - , subTitle : Maybe String + { close : msg , content : List (Html msg) , footer : List (Html msg) , formAction : Maybe msg + , noOp : msg + , size : Size + , subTitle : Maybe String + , title : String } @@ -33,8 +33,8 @@ view config = , custom "mouseup" (Decode.succeed { message = config.noOp - , stopPropagation = True , preventDefault = True + , stopPropagation = True } ) ] @@ -56,8 +56,8 @@ view config = , custom "mouseup" (Decode.succeed { message = config.close - , stopPropagation = True , preventDefault = True + , stopPropagation = True } ) ] diff --git a/src/Views/Page.elm b/src/Views/Page.elm index 62feee1b9..b0c93cebc 100644 --- a/src/Views/Page.elm +++ b/src/Views/Page.elm @@ -39,8 +39,8 @@ type ActivePage type MenuLink - = Internal String Route.Route ActivePage - | External String String + = External String String + | Internal String Route.Route ActivePage | MailTo String String @@ -147,7 +147,7 @@ mainMenuLinks { enableFoodSection } = secondaryMenuLinks : List MenuLink secondaryMenuLinks = - [ Internal "Changelog" Route.Changelog Changelog + [ Internal "Versions" Route.Changelog Changelog , Internal "Statistiques" Route.Stats Stats , External "Documentation" Env.gitbookUrl , External "Communauté" Env.communityUrl @@ -191,14 +191,14 @@ pageFooter session = let makeLink link = case link of - Internal label route _ -> - Link.internal [ class "text-decoration-none", Route.href route ] - [ text label ] - External label url -> Link.external [ class "text-decoration-none", href url ] [ text label ] + Internal label route _ -> + Link.internal [ class "text-decoration-none", Route.href route ] + [ text label ] + MailTo label email -> a [ class "text-decoration-none", href <| "mailto:" ++ email ] [ text label ] @@ -263,7 +263,12 @@ pageFooter session = |> List.map (List.singleton >> li []) |> List.intersperse (li [ attribute "aria-hidden" "true", class "text-muted" ] [ text "|" ]) |> ul [ class "FooterLegal d-flex justify-content-start flex-wrap gap-2 list-unstyled mt-3 pt-2 border-top" ] - , versionLink session.currentVersion + , div [ class "d-flex align-items-center gap-1 fs-9 mb-2" ] + [ versionLink session.currentVersion + , text "(" + , Link.internal [ Route.href Route.Changelog ] [ text "changelog" ] + , text ")" + ] ] ] @@ -274,13 +279,11 @@ versionLink version = Version versionData -> let displayLink url linkText = - p [ class "fs-9 text-muted" ] - [ Link.external - [ class "text-decoration-none" - , href url - ] - [ text <| "Version\u{00A0}: " ++ linkText ] + Link.external + [ class "text-decoration-none" + , href url ] + [ text <| "Version\u{00A0}: " ++ linkText ] in case ( versionData.hash, versionData.tag ) of -- If we have a tag provided, display it by default @@ -373,6 +376,10 @@ pageHeader { session, activePage, openMobileNavigation, loadUrl, switchVersion } viewNavigationLink : ActivePage -> MenuLink -> Html msg viewNavigationLink activePage link = case link of + External label url -> + Link.external [ class "nav-link link-external-muted", href url ] + [ text label ] + Internal label route page -> Link.internal (class "nav-link" @@ -387,10 +394,6 @@ viewNavigationLink activePage link = ) [ text label ] - External label url -> - Link.external [ class "nav-link link-external-muted", href url ] - [ text label ] - MailTo label email -> a [ class "nav-link", href <| "mailto:" ++ email ] [ text label ] diff --git a/src/Views/RangeSlider.elm b/src/Views/RangeSlider.elm index 0dd6f820e..1caac70c3 100644 --- a/src/Views/RangeSlider.elm +++ b/src/Views/RangeSlider.elm @@ -1,5 +1,6 @@ module Views.RangeSlider exposing ( percent + , physicalDurability , surfaceMass , yarnSize ) @@ -12,22 +13,20 @@ import Html.Events exposing (..) type alias PercentConfig msg = - { id : String + { disabled : Bool + , id : String + , max : Int + , min : Int + , toString : Split -> String , update : Maybe Split -> msg , value : Split - , toString : Split -> String - , disabled : Bool - , min : Int - , max : Int } percent : PercentConfig msg -> Html msg percent config = - layout - { id = config.id - , label = config.toString config.value - , attributes = + narrowLayout + { attributes = [ onInput (String.toFloat >> Maybe.andThen (Split.fromPercent >> Result.toMaybe) >> config.update) , Attr.min (String.fromInt config.min) , Attr.max (String.fromInt config.max) @@ -38,24 +37,24 @@ percent config = , value (String.fromFloat (Split.toPercent config.value)) , Attr.disabled config.disabled ] + , id = config.id + , label = config.toString config.value } type alias SurfaceMassConfig msg = - { id : String + { disabled : Bool + , id : String + , toString : Unit.SurfaceMass -> String , update : Maybe Unit.SurfaceMass -> msg , value : Unit.SurfaceMass - , toString : Unit.SurfaceMass -> String - , disabled : Bool } surfaceMass : SurfaceMassConfig msg -> Html msg surfaceMass config = - layout - { id = config.id - , label = config.toString config.value - , attributes = + narrowLayout + { attributes = [ onInput (String.toInt >> Maybe.map Unit.gramsPerSquareMeter >> config.update) , Attr.min (String.fromInt (Unit.surfaceMassInGramsPerSquareMeters Unit.minSurfaceMass)) , Attr.max (String.fromInt (Unit.surfaceMassInGramsPerSquareMeters Unit.maxSurfaceMass)) @@ -66,24 +65,24 @@ surfaceMass config = , value (String.fromInt (Unit.surfaceMassInGramsPerSquareMeters config.value)) , Attr.disabled config.disabled ] + , id = config.id + , label = config.toString config.value } type alias YarnSizeConfig msg = - { id : String + { disabled : Bool + , id : String + , toString : Unit.YarnSize -> String , update : Maybe Unit.YarnSize -> msg , value : Unit.YarnSize - , toString : Unit.YarnSize -> String - , disabled : Bool } yarnSize : YarnSizeConfig msg -> Html msg yarnSize config = - layout - { id = config.id - , label = config.toString config.value - , attributes = + narrowLayout + { attributes = [ onInput (String.toFloat >> Maybe.map Unit.yarnSizeKilometersPerKg >> config.update) , Attr.min (String.fromFloat (Unit.yarnSizeInKilometers Unit.minYarnSize)) , Attr.max (String.fromFloat (Unit.yarnSizeInKilometers Unit.maxYarnSize)) @@ -94,24 +93,69 @@ yarnSize config = , value (String.fromFloat (Unit.yarnSizeInKilometers config.value)) , Attr.disabled config.disabled ] + , id = config.id + , label = config.toString config.value + } + + +type alias PhysicalDurabilityConfig msg = + { disabled : Bool + , id : String + , toString : Unit.PhysicalDurability -> String + , update : Maybe Unit.PhysicalDurability -> msg + , value : Unit.PhysicalDurability + } + + +physicalDurability : PhysicalDurabilityConfig msg -> Html msg +physicalDurability config = + wideLayout + { attributes = + [ onInput (String.toFloat >> Maybe.map Unit.physicalDurability >> config.update) + , Attr.min (String.fromFloat (Unit.physicalDurabilityToFloat (Unit.minDurability Unit.PhysicalDurability))) + , Attr.max (String.fromFloat (Unit.physicalDurabilityToFloat (Unit.maxDurability Unit.PhysicalDurability))) + + -- WARNING: be careful when reordering attributes: for obscure reasons, + -- the `value` one MUST be set AFTER the `step` one. + , step "0.01" + , value (String.fromFloat (Unit.physicalDurabilityToFloat config.value)) + , Attr.disabled config.disabled + ] + , id = config.id + , label = config.toString config.value } -layout : { id : String, label : String, attributes : List (Attribute msg) } -> Html msg -layout { id, label, attributes } = +narrowLayout : { attributes : List (Attribute msg), id : String, label : String } -> Html msg +narrowLayout { attributes, id, label } = div [ class "RangeSlider row" ] [ div [ class "col-xxl-6" ] [ Html.label [ for id, class "form-label text-nowrap fs-7 mb-0" ] [ text label ] ] , div [ class "col-xxl-6" ] - [ input - (type_ "range" - :: class "d-block form-range" - :: style "margin-top" "2px" - :: Attr.id id - :: attributes - ) - [] + [ rangeInput (Attr.id id :: attributes) ] + ] + + +wideLayout : { attributes : List (Attribute msg), id : String, label : String } -> Html msg +wideLayout { attributes, id, label } = + div [ class "RangeSlider row", style "flex-grow" "1" ] + [ div [ class "col-xxl-2" ] + [ Html.label [ for id, class "form-label text-nowrap mb-0" ] + [ text label ] ] + , div [ class "col-xxl-10" ] + [ rangeInput (Attr.id id :: attributes) ] ] + + +rangeInput : List (Attribute msg) -> Html msg +rangeInput attributes = + input + (type_ "range" + :: class "d-block form-range" + :: style "margin-top" "2px" + :: attributes + ) + [] diff --git a/src/Views/Score.elm b/src/Views/Score.elm index 67721a965..cc6da3fd3 100644 --- a/src/Views/Score.elm +++ b/src/Views/Score.elm @@ -9,8 +9,8 @@ import Views.Format as Format type alias Config msg = - { impactDefinition : Definition - , customInfo : Maybe (Html msg) + { customInfo : Maybe (Html msg) + , impactDefinition : Definition , mass : Mass , score : Impacts } diff --git a/src/Views/Sidebar.elm b/src/Views/Sidebar.elm index 4fcc8cc17..6ce0b459a 100644 --- a/src/Views/Sidebar.elm +++ b/src/Views/Sidebar.elm @@ -15,30 +15,22 @@ import Views.Score as ScoreView type alias Config msg = - { session : Session - , scope : Scope - - -- Impact selector - , selectedImpact : Definition - , switchImpact : Result String Trigram -> msg - - -- Score - , customScoreInfo : Maybe (Html msg) - , productMass : Mass - , totalImpacts : Impacts - - -- Impacts tabs - , impactTabsConfig : ImpactTabs.Config msg - - -- Bookmarks - , activeBookmarkTab : BookmarkView.ActiveTab + { activeBookmarkTab : BookmarkView.ActiveTab , bookmarkName : String - , copyToClipBoard : String -> msg , compareBookmarks : msg + , copyToClipBoard : String -> msg + , customScoreInfo : Maybe (Html msg) , deleteBookmark : Bookmark -> msg + , impactTabsConfig : ImpactTabs.Config msg + , productMass : Mass , saveBookmark : msg - , updateBookmarkName : String -> msg + , scope : Scope + , selectedImpact : Definition + , session : Session , switchBookmarkTab : BookmarkView.ActiveTab -> msg + , switchImpact : Result String Trigram -> msg + , totalImpacts : Impacts + , updateBookmarkName : String -> msg } @@ -64,22 +56,22 @@ view config = , ScoreView.view { customInfo = config.customScoreInfo , impactDefinition = config.selectedImpact - , score = config.totalImpacts , mass = config.productMass + , score = config.totalImpacts } , config.impactTabsConfig |> ImpactTabs.view db.definitions , BookmarkView.view - { session = config.session - , activeTab = config.activeBookmarkTab + { activeTab = config.activeBookmarkTab , bookmarkName = config.bookmarkName - , impact = config.selectedImpact - , scope = config.scope - , copyToClipBoard = config.copyToClipBoard , compare = config.compareBookmarks + , copyToClipBoard = config.copyToClipBoard , delete = config.deleteBookmark + , impact = config.selectedImpact , save = config.saveBookmark - , update = config.updateBookmarkName + , scope = config.scope + , session = config.session , switchTab = config.switchBookmarkTab + , update = config.updateBookmarkName } ] diff --git a/src/Views/Table.elm b/src/Views/Table.elm index c73ca23fe..8dc375f31 100644 --- a/src/Views/Table.elm +++ b/src/Views/Table.elm @@ -10,9 +10,9 @@ import Views.Format as Format type alias DataPoint msg = - { name : String + { entryAttributes : List (Attribute msg) + , name : String , value : Float - , entryAttributes : List (Attribute msg) } @@ -47,9 +47,10 @@ percentageTable impactDefinition data = [ table [ class "table table-hover w-100 m-0" ] [ data |> List.map - (\{ name, value, entryAttributes } -> - { name = name + (\{ entryAttributes, name, value } -> + { entryAttributes = entryAttributes , impact = value + , name = name , percent = value / total * 100 , width = if value < 0 then @@ -57,11 +58,10 @@ percentageTable impactDefinition data = else value / maximum * 100 - , entryAttributes = entryAttributes } ) |> List.map - (\{ name, impact, percent, width, entryAttributes } -> + (\{ entryAttributes, impact, name, percent, width } -> let entryTitle = name diff --git a/src/Views/Textile/Step.elm b/src/Views/Textile/Step.elm index ec7524063..9f27fd579 100644 --- a/src/Views/Textile/Step.elm +++ b/src/Views/Textile/Step.elm @@ -49,11 +49,10 @@ import Views.Transport as TransportView type alias Config msg modal = - { db : Db - , addMaterialModal : Maybe Inputs.MaterialInput -> Autocomplete Material -> modal + { addMaterialModal : Maybe Inputs.MaterialInput -> Autocomplete Material -> modal , current : Step , daysOfWear : Duration - , useNbCycles : Int + , db : Db , deleteMaterial : Material -> msg , index : Int , inputs : Inputs @@ -76,6 +75,7 @@ type alias Config msg modal = , updatePrinting : Maybe Printing -> msg , updateSurfaceMass : Maybe Unit.SurfaceMass -> msg , updateYarnSize : Maybe Unit.YarnSize -> msg + , useNbCycles : Int } @@ -115,13 +115,13 @@ airTransportRatioField : Config msg modal -> Html msg airTransportRatioField { current, updateAirTransportRatio } = span [ title "Part de transport aérien pour le transport entre la confection et l'entrepôt en France." ] [ RangeSlider.percent - { id = "airTransportRatio" + { disabled = Step.airTransportDisabled current + , id = "airTransportRatio" + , max = 100 + , min = 0 + , toString = Step.airTransportRatioToString , update = updateAirTransportRatio , value = current.airTransportRatio - , toString = Step.airTransportRatioToString - , disabled = Step.airTransportDisabled current - , min = 0 - , max = 100 } ] @@ -254,6 +254,12 @@ printingFields { current, inputs, updatePrinting } = (\str -> updatePrinting (case Printing.fromString str of + Err _ -> + -- Note: we've most likely received the "Aucune" string value from + -- when the user picked this choice, so it's fair to reset any + -- previously selected printing process. + Nothing + Ok kind -> case inputs.printing of Just printing -> @@ -261,12 +267,6 @@ printingFields { current, inputs, updatePrinting } = Nothing -> Just { kind = kind, ratio = Printing.defaultRatio } - - Err _ -> - -- Note: we've most likely received the "Aucune" string value from - -- when the user picked this choice, so it's fair to reset any - -- previously selected printing process. - Nothing ) ) ] @@ -378,62 +378,60 @@ makingWasteField : Config msg modal -> Html msg makingWasteField { current, inputs, updateMakingWaste } = span [ title "Taux moyen de pertes en confection" ] [ RangeSlider.percent - { id = "makingWaste" + { disabled = not current.enabled + , id = "makingWaste" + , max = Env.maxMakingWasteRatio |> Split.toPercent |> round + , min = Env.minMakingWasteRatio |> Split.toPercent |> round + , toString = Step.makingWasteToString , update = updateMakingWaste , value = inputs.fabricProcess |> Fabric.getMakingWaste inputs.product.making.pcrWaste inputs.makingWaste - , toString = Step.makingWasteToString - , disabled = not current.enabled - , min = Env.minMakingWasteRatio |> Split.toPercent |> round - , max = Env.maxMakingWasteRatio |> Split.toPercent |> round } ] makingDeadStockField : Config msg modal -> Html msg -makingDeadStockField { current, updateMakingDeadStock, showAdvancedFields } = +makingDeadStockField { current, showAdvancedFields, updateMakingDeadStock } = showIf showAdvancedFields <| span [ title "Taux moyen de stocks dormants (vêtements non vendus + produits semi-finis non utilisés) sur l’ensemble de la chaîne de valeur" ] [ RangeSlider.percent - { id = "makingDeadStock" + { disabled = not current.enabled + , id = "makingDeadStock" + , max = Env.maxMakingDeadStockRatio |> Split.toPercent |> round + , min = Env.minMakingDeadStockRatio |> Split.toPercent |> round + , toString = Step.makingDeadStockToString , update = updateMakingDeadStock , value = Maybe.withDefault Env.defaultDeadStock current.makingDeadStock - , toString = Step.makingDeadStockToString - , disabled = not current.enabled - , min = Env.minMakingDeadStockRatio |> Split.toPercent |> round - , max = Env.maxMakingDeadStockRatio |> Split.toPercent |> round } ] surfaceMassField : Config msg modal -> Html msg -surfaceMassField { current, updateSurfaceMass, inputs } = +surfaceMassField { current, inputs, updateSurfaceMass } = div [ class "mt-2" , title "Le grammage de l'étoffe, exprimé en g/m², représente sa masse surfacique." ] [ RangeSlider.surfaceMass - { id = "surface-density" + { disabled = not current.enabled + , id = "surface-density" + , toString = Step.surfaceMassToString , update = updateSurfaceMass , value = current.surfaceMass |> Maybe.withDefault inputs.product.surfaceMass - , toString = Step.surfaceMassToString - - -- Note: hide for knitted products as surface mass doesn't have any impact on them - , disabled = not current.enabled } ] yarnSizeField : Config msg modal -> Html msg -yarnSizeField { current, updateYarnSize, inputs } = +yarnSizeField { current, inputs, updateYarnSize } = span [ title "Le titrage indique la grosseur d’un fil textile" ] [ RangeSlider.yarnSize - { id = "yarnSize" + { disabled = not current.enabled + , id = "yarnSize" + , toString = Step.yarnSizeToString , update = updateYarnSize , value = current.yarnSize |> Maybe.withDefault inputs.product.yarnSize - , toString = Step.yarnSizeToString - , disabled = not current.enabled } ] @@ -485,7 +483,7 @@ isStepUpcycled upcycled label = viewStepImpacts : Definition -> Step -> Html msg -viewStepImpacts selectedImpact { impacts, complementsImpacts } = +viewStepImpacts selectedImpact { complementsImpacts, impacts } = showIf (Quantity.greaterThanZero (Impact.getImpact selectedImpact.trigram impacts)) <| let stepComplementsImpact = @@ -535,12 +533,12 @@ viewMaterials config = , [ span [ class "text-muted d-flex fs-7 gap-3 justify-content-left ElementTransportDistances" ] (transport |> TransportView.viewDetails - { fullWidth = False + { airTransportLabel = Nothing + , fullWidth = False , hideNoLength = True , onlyIcons = False - , airTransportLabel = Nothing - , seaTransportLabel = Nothing , roadTransportLabel = Nothing + , seaTransportLabel = Nothing } ) , span @@ -656,16 +654,16 @@ createElementSelectorConfig cfg materialInput = let materialQuery : MaterialQuery materialQuery = - { id = materialInput.material.id + { country = materialInput.country |> Maybe.map .code + , id = materialInput.material.id , share = materialInput.share , spinning = materialInput.spinning - , country = materialInput.country |> Maybe.map .code } baseElement = - { element = materialInput.material + { country = materialInput.country + , element = materialInput.material , quantity = materialInput.share - , country = materialInput.country } excluded = @@ -680,12 +678,12 @@ createElementSelectorConfig cfg materialInput = { allowEmptyList = False , baseElement = baseElement , db = - { elements = cfg.db.textile.materials - , countries = + { countries = cfg.db.countries |> Scope.only Scope.Textile |> List.sortBy .name , definitions = cfg.db.definitions + , elements = cfg.db.textile.materials } , defaultCountry = materialInput.material.geographicOrigin , delete = cfg.deleteMaterial @@ -693,14 +691,14 @@ createElementSelectorConfig cfg materialInput = , impact = impacts , openExplorerDetails = cfg.openExplorerDetails , quantityView = - \{ quantity, onChange } -> + \{ onChange, quantity } -> SplitInput.view { disabled = False - , share = quantity , onChange = onChange + , share = quantity } - , selectedImpact = cfg.selectedImpact , selectElement = \_ autocompleteState -> cfg.setModal (cfg.addMaterialModal (Just materialInput) autocompleteState) + , selectedImpact = cfg.selectedImpact , toId = .id >> Material.idToString , toString = .shortName , toTooltip = .materialProcess >> .name @@ -709,9 +707,9 @@ createElementSelectorConfig cfg materialInput = cfg.updateMaterial materialQuery { materialQuery - | id = newElement.element.id + | country = newElement.country |> Maybe.map .code + , id = newElement.element.id , share = newElement.quantity - , country = newElement.country |> Maybe.map .code } } @@ -728,12 +726,12 @@ viewTransport ({ selectedImpact, current, inputs } as config) = [ div [ class "d-flex justify-content-between gap-3 flex-column flex-md-row" ] (current.transport |> TransportView.viewDetails - { fullWidth = False + { airTransportLabel = Nothing + , fullWidth = False , hideNoLength = True , onlyIcons = False - , airTransportLabel = Nothing - , seaTransportLabel = Nothing , roadTransportLabel = Nothing + , seaTransportLabel = Nothing } ) , span [] @@ -823,8 +821,8 @@ ennoblingToxicityView db ({ selectedImpact, inputs } as config) current = bleachingToxicity = current.outputMass |> Formula.bleachingImpacts current.impacts - { bleachingProcess = db.textile.wellKnown.bleaching - , aquaticPollutionScenario = current.country.aquaticPollutionScenario + { aquaticPollutionScenario = current.country.aquaticPollutionScenario + , bleachingProcess = db.textile.wellKnown.bleaching } dyeingToxicity = @@ -832,13 +830,13 @@ ennoblingToxicityView db ({ selectedImpact, inputs } as config) current = |> List.map (\{ material, share } -> Formula.materialDyeingToxicityImpacts current.impacts - { dyeingToxicityProcess = + { aquaticPollutionScenario = current.country.aquaticPollutionScenario + , dyeingToxicityProcess = if Origin.isSynthetic material.origin then db.textile.wellKnown.dyeingSynthetic else db.textile.wellKnown.dyeingCellulosic - , aquaticPollutionScenario = current.country.aquaticPollutionScenario } current.outputMass share @@ -853,10 +851,9 @@ ennoblingToxicityView db ({ selectedImpact, inputs } as config) current = WellKnown.getPrintingProcess kind db.textile.wellKnown in current.outputMass - |> Formula.materialPrintingToxicityImpacts - current.impacts - { printingToxicityProcess = printingToxicityProcess - , aquaticPollutionScenario = current.country.aquaticPollutionScenario + |> Formula.materialPrintingToxicityImpacts current.impacts + { aquaticPollutionScenario = current.country.aquaticPollutionScenario + , printingToxicityProcess = printingToxicityProcess } ratio @@ -1078,13 +1075,6 @@ advancedStepView ({ db, inputs, selectedImpact, current } as config) = (List.map (\line -> li [ class "list-group-item fs-7" ] [ line ]) (case current.label of - Label.Spinning -> - [ yarnSizeField config - ] - - Label.Fabric -> - [ surfaceMassField config ] - Label.Ennobling -> [ div [ class "mb-2" ] [ text "Pré-traitement\u{00A0}: non applicable" ] @@ -1093,6 +1083,9 @@ advancedStepView ({ db, inputs, selectedImpact, current } as config) = [ text "Finition\u{00A0}: apprêt chimique" ] ] + Label.Fabric -> + [ surfaceMassField config ] + Label.Making -> [ makingWasteField config , makingDeadStockField config @@ -1100,6 +1093,10 @@ advancedStepView ({ db, inputs, selectedImpact, current } as config) = , fadingField config ] + Label.Spinning -> + [ yarnSizeField config + ] + Label.Use -> [ daysOfWearInfo config ] @@ -1164,8 +1161,7 @@ advancedStepView ({ db, inputs, selectedImpact, current } as config) = view : Config msg modal -> ViewWithTransport msg view config = - { transport = viewTransport config - , step = + { step = stepView config (if config.current.label == Label.Material then viewMaterials config @@ -1176,4 +1172,5 @@ view config = else regulatoryStepView config ) + , transport = viewTransport config } diff --git a/src/Views/Transport.elm b/src/Views/Transport.elm index e0fe0b730..a5740f4ea 100644 --- a/src/Views/Transport.elm +++ b/src/Views/Transport.elm @@ -9,17 +9,17 @@ import Views.Icon as Icon type alias Config = - { fullWidth : Bool - , onlyIcons : Bool + { airTransportLabel : Maybe String + , fullWidth : Bool , hideNoLength : Bool - , airTransportLabel : Maybe String - , seaTransportLabel : Maybe String + , onlyIcons : Bool , roadTransportLabel : Maybe String + , seaTransportLabel : Maybe String } viewDetails : Config -> Transport -> List (Html msg) -viewDetails { onlyIcons, hideNoLength, airTransportLabel, seaTransportLabel, roadTransportLabel } { air, sea, seaCooled, road, roadCooled } = +viewDetails { airTransportLabel, hideNoLength, onlyIcons, roadTransportLabel, seaTransportLabel } { air, road, roadCooled, sea, seaCooled } = [ { distance = air, icon = Icon.plane, label = Maybe.withDefault "Transport aérien" airTransportLabel } , { distance = sea, icon = Icon.boat, label = Maybe.withDefault "Transport maritime" seaTransportLabel } , { distance = seaCooled, icon = Icon.boatCooled, label = "Transport maritime réfrigéré" } @@ -32,20 +32,20 @@ viewDetails { onlyIcons, hideNoLength, airTransportLabel, seaTransportLabel, roa Nothing else - Just <| entry { onlyIcons = onlyIcons, distance = distance, icon = icon, label = label } + Just <| entry { distance = distance, icon = icon, label = label, onlyIcons = onlyIcons } ) type alias EntryConfig msg = - { onlyIcons : Bool - , distance : Length + { distance : Length , icon : Html msg , label : String + , onlyIcons : Bool } entry : EntryConfig msg -> Html msg -entry { onlyIcons, distance, icon, label } = +entry { distance, icon, label, onlyIcons } = span [ class "d-flex align-items-center gap-1", title label ] [ span [ style "cursor" "help" ] [ icon ] diff --git a/tests/Data/GitbookTest.elm b/tests/Data/GitbookTest.elm deleted file mode 100644 index 255d7d141..000000000 --- a/tests/Data/GitbookTest.elm +++ /dev/null @@ -1,30 +0,0 @@ -module Data.GitbookTest exposing (..) - -import Data.Env as Env -import Data.Gitbook as Gitbook -import Expect -import Test exposing (..) -import TestUtils exposing (asTest) - - -suite : Test -suite = - describe "Data.Gitbook" - [ describe "handleMarkdownLink" - [ Gitbook.handleMarkdownGitbookLink Nothing "http://google.com" - |> Expect.equal "http://google.com" - |> asTest "should resolve an external link" - , Gitbook.handleMarkdownGitbookLink (Just Gitbook.TextileSpinning) "http://google.com" - |> Expect.equal "http://google.com" - |> asTest "should resolve an external link even with a path provided" - , Gitbook.handleMarkdownGitbookLink (Just Gitbook.TextileSpinning) "filature.md" - |> Expect.equal (Env.gitbookUrl ++ "/textile/filature") - |> asTest "should resolve an internal link from current page path" - , Gitbook.handleMarkdownGitbookLink (Just Gitbook.TextileSpinning) "../faq.md" - |> Expect.equal (Env.gitbookUrl ++ "/textile/../faq") - |> asTest "should resolve an internal link from current page path down a folder level" - , Gitbook.handleMarkdownGitbookLink (Just Gitbook.TextileSpinning) "foo/bar.md" - |> Expect.equal (Env.gitbookUrl ++ "/textile/foo/bar") - |> asTest "should resolve an internal link from current page path up a folder level" - ] - ] diff --git a/tests/Data/Textile/EconomicsTest.elm b/tests/Data/Textile/EconomicsTest.elm index 1e988395a..33da548ab 100644 --- a/tests/Data/Textile/EconomicsTest.elm +++ b/tests/Data/Textile/EconomicsTest.elm @@ -17,14 +17,14 @@ suite : Test suite = describe "Data.Textile.Economics" [ describe "computeDurabilityIndex" - [ Economics.computeDurabilityIndex + [ Economics.computeNonPhysicalDurabilityIndex { business = SmallBusiness , numberOfReferences = 20000 , price = priceFromFloat 100 , repairCost = priceFromFloat 10 , traceability = False } - |> Unit.durabilityToFloat + |> Unit.nonPhysicalDurabilityToFloat |> Expect.within (Expect.Absolute 0.01) 1.1 |> asTest "should compute durability index" ] diff --git a/tests/Data/Textile/FormulaTest.elm b/tests/Data/Textile/FormulaTest.elm index 75149d62a..3665eb21e 100644 --- a/tests/Data/Textile/FormulaTest.elm +++ b/tests/Data/Textile/FormulaTest.elm @@ -29,6 +29,7 @@ km = noOpProcess : Process noOpProcess = { name = "Default" + , displayName = Just "Default" , info = "" , unit = "" , uuid = Process.Uuid "" diff --git a/tests/Data/UnitTest.elm b/tests/Data/UnitTest.elm index a340e35ec..f52fce313 100644 --- a/tests/Data/UnitTest.elm +++ b/tests/Data/UnitTest.elm @@ -20,7 +20,7 @@ suite = describe "Data.Unit" [ describe "Decoder validation" [ "-7" - |> Decode.decodeString Unit.decodeDurability + |> Decode.decodeString Unit.decodePhysicalDurability |> Result.mapError Decode.errorToString |> Expect.err |> asTest "should discard erroneous Durability value" diff --git a/tests/Server/RouteTest.elm b/tests/Server/RouteTest.elm index ab62ef2c3..9ea98dab0 100644 --- a/tests/Server/RouteTest.elm +++ b/tests/Server/RouteTest.elm @@ -42,13 +42,13 @@ foodEndpoints : StaticDb.Db -> List Test foodEndpoints db = [ describe "GET endpoints" [ testEndpoint db "GET" Encode.null "/food/ingredients" - |> Expect.equal (Just Route.GetFoodIngredientList) + |> Expect.equal (Just Route.FoodGetIngredientList) |> asTest "should map the /food/ingredients endpoint" , testEndpoint db "GET" Encode.null "/food/transforms" - |> Expect.equal (Just Route.GetFoodTransformList) + |> Expect.equal (Just Route.FoodGetTransformList) |> asTest "should map the /food/transforms endpoint" , testEndpoint db "GET" Encode.null "/food/packagings" - |> Expect.equal (Just Route.GetFoodPackagingList) + |> Expect.equal (Just Route.FoodGetPackagingList) |> asTest "should map the /food/packagings endpoint" , [ "/food?" , "ingredients[]=flour;97" @@ -69,17 +69,17 @@ foodEndpoints db = ] |> String.join "&" |> testEndpoint db "GET" Encode.null - |> Expect.equal (Just <| Route.GetFoodRecipe (Ok Fixtures.royalPizza)) + |> Expect.equal (Just <| Route.FoodGetRecipe (Ok Fixtures.royalPizza)) |> asTest "should map the /food endpoint" ] , describe "POST endpoints" [ "/food" |> testEndpoint db "POST" (FoodQuery.encode FoodQuery.empty) - |> Expect.equal (Just Route.PostFoodRecipe) + |> Expect.equal (Just Route.FoodPostRecipe) |> asTest "should map the POST /food endpoint" , "/food" |> testEndpoint db "POST" Encode.null - |> Expect.equal (Just Route.PostFoodRecipe) + |> Expect.equal (Just Route.FoodPostRecipe) |> asTest "should map the POST /food endpoint whatever the request body is" ] , describe "validation" @@ -164,7 +164,7 @@ textileEndpoints db = , "countryMaking=FR" ] |> testEndpoint db "GET" Encode.null - |> Expect.equal (Just <| Route.GetTextileSimulator (Ok sampleQuery)) + |> Expect.equal (Just <| Route.TextileGetSimulator (Ok sampleQuery)) |> asTest "should map the /textile/simulator endpoint" , [ "/textile/simulator?mass=0.17" , "product=tshirt" @@ -178,7 +178,7 @@ textileEndpoints db = |> testEndpoint db "GET" Encode.null |> Expect.equal (Just <| - Route.GetTextileSimulator <| + Route.TextileGetSimulator <| Ok { sampleQuery | disabledSteps = [ Label.Making, Label.Ennobling ] } ) |> asTest "should map the /textile/simulator endpoint with the disabledSteps parameter set" @@ -193,7 +193,7 @@ textileEndpoints db = |> testEndpoint db "GET" Encode.null |> Expect.equal (Just <| - Route.GetTextileSimulatorSingle Definition.Fwe <| + Route.TextileGetSimulatorSingle Definition.Fwe <| Ok sampleQuery ) |> asTest "should map the /textile/simulator/{impact} endpoint" @@ -208,7 +208,7 @@ textileEndpoints db = |> testEndpoint db "GET" Encode.null |> Expect.equal (Just <| - Route.GetTextileSimulatorDetailed <| + Route.TextileGetSimulatorDetailed <| Ok sampleQuery ) |> asTest "should map the /textile/simulator/detailed endpoint" @@ -216,11 +216,11 @@ textileEndpoints db = , describe "POST endpoints" [ "/textile/simulator" |> testEndpoint db "POST" (Query.encode tShirtCotonFrance) - |> Expect.equal (Just Route.PostTextileSimulator) + |> Expect.equal (Just Route.TextilePostSimulator) |> asTest "should map the POST /textile/simulator endpoint" , "/textile/simulator" |> testEndpoint db "POST" Encode.null - |> Expect.equal (Just Route.PostTextileSimulator) + |> Expect.equal (Just Route.TextilePostSimulator) |> asTest "should map the POST /textile/simulator endpoint whatever the request body is" ] , describe "materials param checks" @@ -330,6 +330,11 @@ textileEndpoints db = |> Maybe.andThen (Dict.get "countryDyeing") |> Expect.equal (Just "Le code pays US n'est pas utilisable dans un contexte Textile.") |> asTest "should validate that an ingredient country scope is valid" + , testEndpoint db "GET" Encode.null "/textile/simulator?physicalDurability=99" + |> Maybe.andThen extractTextileErrors + |> Maybe.andThen (Dict.get "physicalDurability") + |> Expect.equal (Just "La durabilité doit être comprise entre 0.67 et 1.45.") + |> asTest "should validate that the physical durability param is invalid" ] , describe "multiple parameters checks" [ testEndpoint db "GET" Encode.null "/textile/simulator" @@ -397,7 +402,7 @@ testEndpoint dbs method body url = extractQuery : Route.Route -> Maybe Query extractQuery route = case route of - Route.GetTextileSimulator (Ok query) -> + Route.TextileGetSimulator (Ok query) -> Just query _ -> @@ -407,7 +412,7 @@ extractQuery route = extractFoodErrors : Route.Route -> Maybe (Dict String String) extractFoodErrors route = case route of - Route.GetFoodRecipe (Err errors) -> + Route.FoodGetRecipe (Err errors) -> Just errors _ -> @@ -417,7 +422,7 @@ extractFoodErrors route = extractTextileErrors : Route.Route -> Maybe (Dict String String) extractTextileErrors route = case route of - Route.GetTextileSimulator (Err errors) -> + Route.TextileGetSimulator (Err errors) -> Just errors _ -> diff --git a/tests/Views/MarkdownTest.elm b/tests/Views/MarkdownTest.elm index 920cc3fea..b77c4588e 100644 --- a/tests/Views/MarkdownTest.elm +++ b/tests/Views/MarkdownTest.elm @@ -1,6 +1,5 @@ module Views.MarkdownTest exposing (..) -import Data.Gitbook as Gitbook import Expect import Html exposing (..) import Test exposing (..) @@ -8,15 +7,6 @@ import TestUtils exposing (asTest) import Views.Markdown as Markdown -gitbookPage : String -> Gitbook.Page -gitbookPage md = - { title = "" - , description = Nothing - , path = Gitbook.TextileUse - , markdown = md - } - - suite : Test suite = describe "Views.Markdown" @@ -25,19 +15,8 @@ suite = -- making it super hard to debug. I couldn't identify any solution to this, -- yet it's important to have this test ensuring the very basics work. [ "plop" - |> Markdown.Simple |> Markdown.parse |> Expect.equal (Ok [ p [] [ text "plop" ] ]) |> asTest "should parse the simplest Markdown string" - , gitbookPage "plop" - |> Markdown.Gitbook - |> Markdown.parse - |> Expect.equal (Ok [ p [] [ text "plop" ] ]) - |> asTest "should parse the simplest Gitbook page Markdown string" - , gitbookPage "Foo & Bar" - |> Markdown.Gitbook - |> Markdown.parse - |> Expect.equal (Ok [ p [] [ text "Foo & Bar" ] ]) - |> asTest "should handle Gitbook page HTML entities in Markdown string" ] ] diff --git a/tests/server.spec.js b/tests/server.spec.js index af8e88318..677ee0303 100644 --- a/tests/server.spec.js +++ b/tests/server.spec.js @@ -229,6 +229,14 @@ describe("API", () => { ); }); + it("should validate the physicalDurability param range", async () => { + expectFieldErrorMessage( + await makeRequest("/api/textile/simulator", ["physicalDurability=2"]), + "physicalDurability", + /doit être comprise entre/, + ); + }); + it("should accept the yarnSize param without any unit", async () => { const response = await makeRequest("/api/textile/simulator", ["yarnSize=9"]); });