ecosemantic-databases 0.1.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ecosemantic_databases-0.1.8/.gitignore +24 -0
- ecosemantic_databases-0.1.8/LICENSE +73 -0
- ecosemantic_databases-0.1.8/NOTICE +13 -0
- ecosemantic_databases-0.1.8/PKG-INFO +81 -0
- ecosemantic_databases-0.1.8/README.md +45 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/__init__.py +1 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/__init__.py +1 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/build.py +44 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/cli.py +124 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/differences.py +52 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/download.py +142 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/probe.py +168 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/profile.py +91 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/releases/2026-v1.yaml +163 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/releases/differences-2026-v1.csv +12403 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/releases/unlinked-2026-v1.csv +1 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/setup.py +146 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/validate.py +323 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/bafu/workbook.py +186 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/checks.py +27 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/cli.py +33 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/command.py +57 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/__init__.py +1 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/build.py +63 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/cli.py +161 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/datasets.py +290 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/download.py +69 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/lcia.py +83 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/masterdata.py +119 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/overview.py +39 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/probe.py +125 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/profile.py +139 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/py.typed +0 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/release.py +52 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/releases/3.10-cutoff.yaml +67 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/releases/3.10.1-cutoff.yaml +69 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/releases/3.11-cutoff.yaml +67 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/releases/3.12-cutoff.yaml +71 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/setup.py +85 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/ecoinvent/validate.py +732 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/lists.py +219 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/profiles.py +43 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/py.typed +0 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/scores.py +136 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/__init__.py +1 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/build.py +554 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/cli.py +124 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/download.py +92 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/probe.py +216 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/profile.py +68 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/releases/1.2026-09.0.yaml +24 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/releases/left-out-1.2026-09.0.csv +50 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/releases/unlinked-1.2026-09.0.csv +3230 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/setup.py +98 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/uslci/validate.py +292 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/__init__.py +1 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/derby/__init__.py +1 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/derby/catalog.py +272 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/derby/database.py +95 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/derby/log.py +116 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/derby/numbers.py +66 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/derby/pages.py +269 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/derby/records.py +210 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/derby/values.py +201 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/export/__init__.py +1 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/export/entities.py +527 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/export/geometry.py +146 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/export/refs.py +102 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/export/render.py +79 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/export/tables.py +77 -0
- ecosemantic_databases-0.1.8/ecosemantic/databases/zolca/export/write.py +174 -0
- ecosemantic_databases-0.1.8/pyproject.toml +53 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.venv/
|
|
4
|
+
venv/
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
node_modules/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
coverage.json
|
|
14
|
+
htmlcov/
|
|
15
|
+
|
|
16
|
+
.env
|
|
17
|
+
.env.*
|
|
18
|
+
!.env.example
|
|
19
|
+
|
|
20
|
+
.DS_Store
|
|
21
|
+
graphify-out
|
|
22
|
+
knowledge/
|
|
23
|
+
.okf-cache.json
|
|
24
|
+
**/ecosemantic/view/static/
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# PolyForm Noncommercial License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
8
|
+
|
|
9
|
+
## Copyright License
|
|
10
|
+
|
|
11
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
12
|
+
|
|
13
|
+
## Distribution License
|
|
14
|
+
|
|
15
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
16
|
+
|
|
17
|
+
## Notices
|
|
18
|
+
|
|
19
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
20
|
+
|
|
21
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
22
|
+
|
|
23
|
+
## Changes and New Works License
|
|
24
|
+
|
|
25
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
26
|
+
|
|
27
|
+
## Patent License
|
|
28
|
+
|
|
29
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
30
|
+
|
|
31
|
+
## Noncommercial Purposes
|
|
32
|
+
|
|
33
|
+
Any noncommercial purpose is a permitted purpose.
|
|
34
|
+
|
|
35
|
+
## Personal Uses
|
|
36
|
+
|
|
37
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
38
|
+
|
|
39
|
+
## Noncommercial Organizations
|
|
40
|
+
|
|
41
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
42
|
+
|
|
43
|
+
## Fair Use
|
|
44
|
+
|
|
45
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
46
|
+
|
|
47
|
+
## No Other Rights
|
|
48
|
+
|
|
49
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
50
|
+
|
|
51
|
+
## Patent Defense
|
|
52
|
+
|
|
53
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
54
|
+
|
|
55
|
+
## Violations
|
|
56
|
+
|
|
57
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
58
|
+
|
|
59
|
+
## No Liability
|
|
60
|
+
|
|
61
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
62
|
+
|
|
63
|
+
## Definitions
|
|
64
|
+
|
|
65
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
66
|
+
|
|
67
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
68
|
+
|
|
69
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
70
|
+
|
|
71
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
72
|
+
|
|
73
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
EcoSemantic
|
|
2
|
+
Required Notice: Copyright 2026 Carlos Gaete-Morales (https://github.com/cdgaete/ecosemantic)
|
|
3
|
+
|
|
4
|
+
EcoSemantic is licensed under the PolyForm Noncommercial License 1.0.0, in LICENSE
|
|
5
|
+
and at https://polyformproject.org/licenses/noncommercial/1.0.0. Any noncommercial
|
|
6
|
+
purpose is permitted. Commercial use needs a separate licence from Carlos
|
|
7
|
+
Gaete-Morales: cdgaete@gmail.com.
|
|
8
|
+
|
|
9
|
+
The page that ecosemantic-view carries bundles third-party software under its
|
|
10
|
+
own licences; their notices are in ecosemantic/view/static/THIRD_PARTY_NOTICES.txt
|
|
11
|
+
inside that distribution. Components of the page adapt shadcn/ui, Copyright (c)
|
|
12
|
+
2023 shadcn, under the MIT licence, as page/PROVENANCE.md in the source
|
|
13
|
+
repository records.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ecosemantic-databases
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: Native EcoSemantic bundles built from a source database's own files.
|
|
5
|
+
Project-URL: Homepage, https://github.com/cdgaete/ecosemantic
|
|
6
|
+
Project-URL: Repository, https://github.com/cdgaete/ecosemantic
|
|
7
|
+
Project-URL: Issues, https://github.com/cdgaete/ecosemantic/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/cdgaete/ecosemantic/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Carlos Gaete-Morales <cdgaete@gmail.com>
|
|
10
|
+
License-Expression: PolyForm-Noncommercial-1.0.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
License-File: NOTICE
|
|
13
|
+
Keywords: EcoSpold2,LCA,ecoinvent,life cycle assessment,openLCA
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: ecosemantic-core==0.1.8
|
|
25
|
+
Requires-Dist: ecosemantic-engine==0.1.8
|
|
26
|
+
Requires-Dist: lca-data-interface~=0.1
|
|
27
|
+
Requires-Dist: numpy>=2.5
|
|
28
|
+
Requires-Dist: olca-schema==2.4.0
|
|
29
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
30
|
+
Requires-Dist: pyyaml<7,>=6.0.1
|
|
31
|
+
Requires-Dist: scipy>=1.16
|
|
32
|
+
Provides-Extra: ecoinvent
|
|
33
|
+
Requires-Dist: ecoinvent-interface==3.1; extra == 'ecoinvent'
|
|
34
|
+
Requires-Dist: python-calamine<1,>=0.3; extra == 'ecoinvent'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# ecosemantic-databases
|
|
38
|
+
|
|
39
|
+
The sources a background database installs from, each read from its own release files and written as an openLCA JSON-LD bundle the core installs: the ecoinvent builder with its release profiles, the USLCI source, the BAFU source, and the reader for an openLCA `.zolca` database.
|
|
40
|
+
|
|
41
|
+
## Names it publishes
|
|
42
|
+
|
|
43
|
+
- `ecosemantic.databases.ecoinvent` — the builder: a release's EcoSpold2 datasets, its MasterData and its LCIA implementation, read and nothing else; no Brightway, no bw2io. It downloads the release, probes what it holds against what a build reads, writes one openLCA JSON-LD bundle, and validates the bundle against ecoinvent's published results. `releases/` beside it holds one profile per validated release: file hashes, workbook layouts, the decisions the probe asked for, and the measured validation numbers.
|
|
44
|
+
- `ecosemantic.databases.uslci` — the USLCI source: the release the profile pins, downloaded from the Federal LCA Commons through `lca-data-interface`, with its profile, its two lists and its notices.
|
|
45
|
+
- `ecosemantic.databases.bafu` — the BAFU source: the release the profile pins, read through an openLCA Nexus session, with its profile and the terms an install accepts.
|
|
46
|
+
- `ecosemantic.databases.zolca` — the reader for an openLCA `.zolca` database.
|
|
47
|
+
- `ecosemantic.databases.profiles`, `lists`, `checks` and `scores` — the profiles a source pins, the lists a release is filed under, the checks a bundle passes and the scores it is validated against.
|
|
48
|
+
- The `ecosemantic-databases` command, over the `bafu`, `ecoinvent` and `uslci` groups.
|
|
49
|
+
|
|
50
|
+
## The command
|
|
51
|
+
|
|
52
|
+
`ecosemantic-databases` runs one step of an ecoinvent build on its own, for a person who wants the files; `setup ecoinvent` runs the same steps and installs what they build.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ecosemantic-databases ecoinvent download 3.12 --system-model cutoff
|
|
56
|
+
ecosemantic-databases ecoinvent probe 3.12 --system-model cutoff
|
|
57
|
+
ecosemantic-databases ecoinvent build 3.12 --system-model cutoff --out builds
|
|
58
|
+
ecosemantic-databases ecoinvent validate 3.12 --system-model cutoff --bundle builds/ecoinvent-3.12-cutoff.zip
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`download` fetches the release's five files and prints each path with the SHA-256 of every file a build reads; `probe` lists what the release holds against what a build reads and the gaps between them; `build` writes `<dir>/ecoinvent-<version>-<model>.zip`; `validate` compiles the bundle for the engine and compares its scores with the matrix export and the cumulative LCIA workbook. `build` and `validate` read `releases/<version>-<model>.yaml` when the release has one, and take the default layout with `--no-profile`. `uslci` and `bafu` take the same four steps over their own releases.
|
|
62
|
+
|
|
63
|
+
## What it depends on
|
|
64
|
+
|
|
65
|
+
`ecosemantic-engine` and `ecosemantic-core` at the same version; `lca-data-interface` for the openLCA Nexus and Federal LCA Commons clients; and pydantic, olca-schema, pyyaml, numpy and scipy. The `ecoinvent` extra adds `ecoinvent_interface` and `python-calamine`.
|
|
66
|
+
|
|
67
|
+
## Credentials
|
|
68
|
+
|
|
69
|
+
`ecoinvent_interface` reads `EI_USERNAME` and `EI_PASSWORD` from the environment, and a download refuses with `Unavailable` when either is absent. The BAFU source reads `NEXUS_USERNAME` and `NEXUS_PASSWORD`, and `NEXUS_TOKEN` for an account with a second factor.
|
|
70
|
+
|
|
71
|
+
## Where the files live
|
|
72
|
+
|
|
73
|
+
Downloaded releases stay in `ecoinvent_interface`'s cache, `~/.local/share/EcoinventInterface/cache`, and built bundles go to the directory `build` takes. Neither is tracked. `releases/<version>-<model>.yaml` holds one profile per validated release, and no release data enters this distribution.
|
|
74
|
+
|
|
75
|
+
## Installing it
|
|
76
|
+
|
|
77
|
+
`pip install ecosemantic` installs this distribution with the other five, its `ecoinvent` extra included, and the `ecosemantic` command. `pip install "ecosemantic-databases[ecoinvent]"` installs the builder alone, with the dependencies an ecoinvent build needs.
|
|
78
|
+
|
|
79
|
+
## Licence
|
|
80
|
+
|
|
81
|
+
PolyForm Noncommercial License 1.0.0, copyright Carlos Gaete-Morales: `LICENSE` and `NOTICE` in this distribution. Any noncommercial purpose is permitted, by individuals and by charities, schools, public research and government bodies; commercial use needs a separate licence from him (cdgaete@gmail.com). EcoSemantic is his trademark, and the licence grants no right to use that name (`TRADEMARK.md` in the repository). This distribution handles licensed data and credentials and carries no release data. The code is published from https://github.com/cdgaete/ecosemantic.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# ecosemantic-databases
|
|
2
|
+
|
|
3
|
+
The sources a background database installs from, each read from its own release files and written as an openLCA JSON-LD bundle the core installs: the ecoinvent builder with its release profiles, the USLCI source, the BAFU source, and the reader for an openLCA `.zolca` database.
|
|
4
|
+
|
|
5
|
+
## Names it publishes
|
|
6
|
+
|
|
7
|
+
- `ecosemantic.databases.ecoinvent` — the builder: a release's EcoSpold2 datasets, its MasterData and its LCIA implementation, read and nothing else; no Brightway, no bw2io. It downloads the release, probes what it holds against what a build reads, writes one openLCA JSON-LD bundle, and validates the bundle against ecoinvent's published results. `releases/` beside it holds one profile per validated release: file hashes, workbook layouts, the decisions the probe asked for, and the measured validation numbers.
|
|
8
|
+
- `ecosemantic.databases.uslci` — the USLCI source: the release the profile pins, downloaded from the Federal LCA Commons through `lca-data-interface`, with its profile, its two lists and its notices.
|
|
9
|
+
- `ecosemantic.databases.bafu` — the BAFU source: the release the profile pins, read through an openLCA Nexus session, with its profile and the terms an install accepts.
|
|
10
|
+
- `ecosemantic.databases.zolca` — the reader for an openLCA `.zolca` database.
|
|
11
|
+
- `ecosemantic.databases.profiles`, `lists`, `checks` and `scores` — the profiles a source pins, the lists a release is filed under, the checks a bundle passes and the scores it is validated against.
|
|
12
|
+
- The `ecosemantic-databases` command, over the `bafu`, `ecoinvent` and `uslci` groups.
|
|
13
|
+
|
|
14
|
+
## The command
|
|
15
|
+
|
|
16
|
+
`ecosemantic-databases` runs one step of an ecoinvent build on its own, for a person who wants the files; `setup ecoinvent` runs the same steps and installs what they build.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
ecosemantic-databases ecoinvent download 3.12 --system-model cutoff
|
|
20
|
+
ecosemantic-databases ecoinvent probe 3.12 --system-model cutoff
|
|
21
|
+
ecosemantic-databases ecoinvent build 3.12 --system-model cutoff --out builds
|
|
22
|
+
ecosemantic-databases ecoinvent validate 3.12 --system-model cutoff --bundle builds/ecoinvent-3.12-cutoff.zip
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`download` fetches the release's five files and prints each path with the SHA-256 of every file a build reads; `probe` lists what the release holds against what a build reads and the gaps between them; `build` writes `<dir>/ecoinvent-<version>-<model>.zip`; `validate` compiles the bundle for the engine and compares its scores with the matrix export and the cumulative LCIA workbook. `build` and `validate` read `releases/<version>-<model>.yaml` when the release has one, and take the default layout with `--no-profile`. `uslci` and `bafu` take the same four steps over their own releases.
|
|
26
|
+
|
|
27
|
+
## What it depends on
|
|
28
|
+
|
|
29
|
+
`ecosemantic-engine` and `ecosemantic-core` at the same version; `lca-data-interface` for the openLCA Nexus and Federal LCA Commons clients; and pydantic, olca-schema, pyyaml, numpy and scipy. The `ecoinvent` extra adds `ecoinvent_interface` and `python-calamine`.
|
|
30
|
+
|
|
31
|
+
## Credentials
|
|
32
|
+
|
|
33
|
+
`ecoinvent_interface` reads `EI_USERNAME` and `EI_PASSWORD` from the environment, and a download refuses with `Unavailable` when either is absent. The BAFU source reads `NEXUS_USERNAME` and `NEXUS_PASSWORD`, and `NEXUS_TOKEN` for an account with a second factor.
|
|
34
|
+
|
|
35
|
+
## Where the files live
|
|
36
|
+
|
|
37
|
+
Downloaded releases stay in `ecoinvent_interface`'s cache, `~/.local/share/EcoinventInterface/cache`, and built bundles go to the directory `build` takes. Neither is tracked. `releases/<version>-<model>.yaml` holds one profile per validated release, and no release data enters this distribution.
|
|
38
|
+
|
|
39
|
+
## Installing it
|
|
40
|
+
|
|
41
|
+
`pip install ecosemantic` installs this distribution with the other five, its `ecoinvent` extra included, and the `ecosemantic` command. `pip install "ecosemantic-databases[ecoinvent]"` installs the builder alone, with the dependencies an ecoinvent build needs.
|
|
42
|
+
|
|
43
|
+
## Licence
|
|
44
|
+
|
|
45
|
+
PolyForm Noncommercial License 1.0.0, copyright Carlos Gaete-Morales: `LICENSE` and `NOTICE` in this distribution. Any noncommercial purpose is permitted, by individuals and by charities, schools, public research and government bodies; commercial use needs a separate licence from him (cdgaete@gmail.com). EcoSemantic is his trademark, and the licence grants no right to use that name (`TRADEMARK.md` in the repository). This distribution handles licensed data and credentials and carries no release data. The code is published from https://github.com/cdgaete/ecosemantic.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Native EcoSemantic bundles built from a source database's own files."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Build a native EcoSemantic bundle from BAFU's own files."""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Build a BAFU zolca into one openLCA JSON-LD bundle, read back by the core's importer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ecosemantic.databases.zolca.export.write import NotCarried, export
|
|
10
|
+
from ecosemantic.errors import Conflict
|
|
11
|
+
from ecosemantic.package.background import read_background
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class Built:
|
|
16
|
+
"""What a build wrote: the bundle, the records it holds by folder, and the rows its package does
|
|
17
|
+
not carry."""
|
|
18
|
+
|
|
19
|
+
bundle: Path
|
|
20
|
+
records: dict[str, int]
|
|
21
|
+
not_carried: tuple[NotCarried, ...]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build(zolca: Path, *, version: str, out_dir: Path) -> Built:
|
|
25
|
+
"""Write a zolca as `<out_dir>/bafu-<version>.zip` and import it under `bafu-<version>`. The
|
|
26
|
+
package is exported to a staging file beside the destination and moved into place only once the
|
|
27
|
+
core reads it, so a bundle the core refuses leaves nothing under the final name; a destination
|
|
28
|
+
that exists is `Conflict`."""
|
|
29
|
+
destination = out_dir / f"bafu-{version}.zip"
|
|
30
|
+
staging = out_dir / f".bafu-{version}.staging.zip"
|
|
31
|
+
if destination.exists():
|
|
32
|
+
raise Conflict(f"{destination.name} exists; name another destination")
|
|
33
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
written = export(zolca, staging)
|
|
35
|
+
try:
|
|
36
|
+
read_background(written.bundle, f"bafu-{version}", foreground=frozenset())
|
|
37
|
+
os.replace(staging, destination)
|
|
38
|
+
finally:
|
|
39
|
+
staging.unlink(missing_ok=True)
|
|
40
|
+
return Built(
|
|
41
|
+
bundle=destination,
|
|
42
|
+
records=written.records,
|
|
43
|
+
not_carried=written.not_carried,
|
|
44
|
+
)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""The command line: the subcommands a BAFU source answers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import cast
|
|
8
|
+
|
|
9
|
+
from lca_data_interface import NexusSession
|
|
10
|
+
|
|
11
|
+
from ecosemantic.databases import command
|
|
12
|
+
from ecosemantic.databases.bafu.build import Built, build
|
|
13
|
+
from ecosemantic.databases.bafu.download import Downloads, Terms, download, translating
|
|
14
|
+
from ecosemantic.databases.bafu.probe import Probe, probe
|
|
15
|
+
from ecosemantic.databases.bafu.profile import RELEASES, read_profile, recorded
|
|
16
|
+
from ecosemantic.databases.bafu.validate import Counted, record, validate
|
|
17
|
+
from ecosemantic.databases.command import Command
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _download(args: argparse.Namespace) -> dict[str, object] | Terms:
|
|
21
|
+
"""Fetch a release's packs through Nexus and answer each file's path and the hash the profile
|
|
22
|
+
pins; while the terms are not accepted, answer the licence file and the citation instead."""
|
|
23
|
+
profile = read_profile(args.version)
|
|
24
|
+
with translating(), NexusSession.from_environment(base=profile.server) as session:
|
|
25
|
+
fetched = download(profile, session, accept_terms=args.accept_terms, results=args.results)
|
|
26
|
+
if isinstance(fetched, Terms):
|
|
27
|
+
return fetched
|
|
28
|
+
return {
|
|
29
|
+
"inventory": {"path": str(fetched.inventory), "sha256": profile.inventory.sha256},
|
|
30
|
+
"results": (
|
|
31
|
+
None
|
|
32
|
+
if fetched.results is None
|
|
33
|
+
else {"path": str(fetched.results), "sha256": profile.results.sha256}
|
|
34
|
+
),
|
|
35
|
+
"licence": str(fetched.licence),
|
|
36
|
+
"citation": fetched.citation,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _probe(args: argparse.Namespace) -> Probe:
|
|
41
|
+
"""Read a zolca and answer what it holds and what the core would not take of it."""
|
|
42
|
+
return probe(Path(args.zolca))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _fetched(version: str) -> Path:
|
|
46
|
+
"""The zolca a release's profile pins, fetched through Nexus with the pack's terms accepted, so
|
|
47
|
+
the answer is always the downloaded file."""
|
|
48
|
+
profile = read_profile(version)
|
|
49
|
+
with translating(), NexusSession.from_environment(base=profile.server) as session:
|
|
50
|
+
fetched = cast(Downloads, download(profile, session, accept_terms=True, results=False))
|
|
51
|
+
return fetched.inventory
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _build(args: argparse.Namespace) -> Built:
|
|
55
|
+
"""Build the zolca given, or the one the release's profile pins, into one bundle under
|
|
56
|
+
`--out`."""
|
|
57
|
+
zolca = Path(args.zolca) if args.zolca is not None else _fetched(args.version)
|
|
58
|
+
return build(zolca, version=args.version, out_dir=Path(args.out))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _validate(args: argparse.Namespace) -> Counted:
|
|
62
|
+
"""Compare a bundle with its release's workbook and the release's two lists, record the run
|
|
63
|
+
when asked, and answer the differences counted by column."""
|
|
64
|
+
profile = read_profile(args.version)
|
|
65
|
+
validation = validate(
|
|
66
|
+
Path(args.bundle),
|
|
67
|
+
results=Path(args.results),
|
|
68
|
+
profile=profile,
|
|
69
|
+
unlinked=recorded(profile.unlinked),
|
|
70
|
+
differences=recorded(profile.differences),
|
|
71
|
+
compiled=Path(args.compiled) if args.compiled is not None else None,
|
|
72
|
+
)
|
|
73
|
+
if args.record:
|
|
74
|
+
record(profile, validation, directory=RELEASES)
|
|
75
|
+
return validation.counted()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _code(answer: object) -> int:
|
|
79
|
+
"""2 for the answer that asks for the pack's terms to be accepted, 1 for a validation that
|
|
80
|
+
found a mismatch, and 0 for an answer that stands."""
|
|
81
|
+
if isinstance(answer, Terms):
|
|
82
|
+
return 2
|
|
83
|
+
return 1 if isinstance(answer, Counted) and bool(answer.mismatches) else 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
COMMANDS: dict[str, Command] = {
|
|
87
|
+
"download": Command("Fetch a release's pinned packs from Nexus.", _download),
|
|
88
|
+
"probe": Command("Read a zolca and list what it holds.", _probe),
|
|
89
|
+
"build": Command("Write a release's bundle and import it.", _build),
|
|
90
|
+
"validate": Command("Compare a bundle with its release's workbook.", _validate),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _parser() -> argparse.ArgumentParser:
|
|
95
|
+
parser = argparse.ArgumentParser(
|
|
96
|
+
prog="ecosemantic-databases bafu",
|
|
97
|
+
description="Build a native EcoSemantic bundle from a BAFU release.",
|
|
98
|
+
)
|
|
99
|
+
subcommands = parser.add_subparsers(dest="command", required=True)
|
|
100
|
+
for name, each in COMMANDS.items():
|
|
101
|
+
sub = subcommands.add_parser(name, help=each.help)
|
|
102
|
+
if name == "download":
|
|
103
|
+
sub.add_argument("version")
|
|
104
|
+
sub.add_argument("--accept-terms", action="store_true")
|
|
105
|
+
sub.add_argument("--results", action="store_true")
|
|
106
|
+
elif name == "probe":
|
|
107
|
+
sub.add_argument("zolca")
|
|
108
|
+
elif name == "build":
|
|
109
|
+
sub.add_argument("version")
|
|
110
|
+
sub.add_argument("--out", required=True)
|
|
111
|
+
sub.add_argument("--zolca")
|
|
112
|
+
elif name == "validate":
|
|
113
|
+
sub.add_argument("version")
|
|
114
|
+
sub.add_argument("--bundle", required=True)
|
|
115
|
+
sub.add_argument("--results", required=True)
|
|
116
|
+
sub.add_argument("--compiled")
|
|
117
|
+
sub.add_argument("--record", action="store_true")
|
|
118
|
+
return parser
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main(argv: list[str] | None = None) -> int:
|
|
122
|
+
"""Run one command; 0 on success, 1 on a typed error or a validation that found a mismatch,
|
|
123
|
+
and 2 when the terms are not accepted."""
|
|
124
|
+
return command.main(_parser(), COMMANDS, argv, code=_code)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""The (process, column) pairs a BAFU validation knows differ beyond its tolerance, keyed by the
|
|
2
|
+
process's id and the workbook column's header so neither BAFU's names nor its values are written
|
|
3
|
+
into the package."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import csv
|
|
8
|
+
from collections.abc import Iterable
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from ecosemantic.errors import Invalid
|
|
13
|
+
|
|
14
|
+
COLUMNS = ("process_id", "column")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class Difference:
|
|
19
|
+
"""One pair whose two scores differ beyond the tolerance."""
|
|
20
|
+
|
|
21
|
+
process_id: str
|
|
22
|
+
column: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _order(row: Difference) -> tuple[str, str]:
|
|
26
|
+
"""A row's sort position: its process, then its column."""
|
|
27
|
+
return (row.process_id, row.column)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def read_differences(path: Path) -> tuple[Difference, ...]:
|
|
31
|
+
"""The pairs a list holds, in process and column order; a pair the list holds twice is
|
|
32
|
+
`Invalid`."""
|
|
33
|
+
with path.open(newline="", encoding="utf-8") as read:
|
|
34
|
+
rows = [
|
|
35
|
+
Difference(process_id=record["process_id"], column=record["column"])
|
|
36
|
+
for record in csv.DictReader(read)
|
|
37
|
+
]
|
|
38
|
+
seen: set[tuple[str, str]] = set()
|
|
39
|
+
for row in rows:
|
|
40
|
+
if _order(row) in seen:
|
|
41
|
+
raise Invalid(f"{path} holds {row.process_id} under {row.column!r} twice")
|
|
42
|
+
seen.add(_order(row))
|
|
43
|
+
return tuple(sorted(rows, key=_order))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def write_differences(path: Path, rows: Iterable[Difference]) -> None:
|
|
47
|
+
"""Write the pairs as CSV, in process and column order."""
|
|
48
|
+
with path.open("w", newline="", encoding="utf-8") as written:
|
|
49
|
+
writer = csv.writer(written, lineterminator="\n")
|
|
50
|
+
writer.writerow(COLUMNS)
|
|
51
|
+
for row in sorted(rows, key=_order):
|
|
52
|
+
writer.writerow((row.process_id, row.column))
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Fetch BAFU's openLCA pack and its LCIA results pack from Nexus, refusing a file whose hash is not
|
|
2
|
+
the one its profile pins."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Iterator
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Final, Protocol
|
|
11
|
+
|
|
12
|
+
from lca_data_interface import Downloaded
|
|
13
|
+
from lca_data_interface import Pack as NexusPack
|
|
14
|
+
from lca_data_interface.errors import (
|
|
15
|
+
CredentialsMissing,
|
|
16
|
+
LcaDataInterfaceError,
|
|
17
|
+
LicenceNotAccepted,
|
|
18
|
+
LoginRefused,
|
|
19
|
+
MissingField,
|
|
20
|
+
NotInCatalog,
|
|
21
|
+
NotOnServer,
|
|
22
|
+
ServiceError,
|
|
23
|
+
ShortDownload,
|
|
24
|
+
TokenRequired,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from ecosemantic.databases.bafu.profile import Pack, Profile
|
|
28
|
+
from ecosemantic.errors import Conflict, EcoSemanticError, Invalid, NotFound, Unavailable
|
|
29
|
+
|
|
30
|
+
_TRANSLATIONS: Final[dict[type[LcaDataInterfaceError], type[EcoSemanticError]]] = {
|
|
31
|
+
CredentialsMissing: Unavailable,
|
|
32
|
+
LoginRefused: Unavailable,
|
|
33
|
+
TokenRequired: Unavailable,
|
|
34
|
+
ShortDownload: Unavailable,
|
|
35
|
+
ServiceError: Unavailable,
|
|
36
|
+
NotInCatalog: NotFound,
|
|
37
|
+
NotOnServer: NotFound,
|
|
38
|
+
MissingField: Invalid,
|
|
39
|
+
LicenceNotAccepted: Invalid,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Nexus(Protocol):
|
|
44
|
+
"""The three calls a download makes on a Nexus session; `NexusSession` satisfies it and a test
|
|
45
|
+
doubles it."""
|
|
46
|
+
|
|
47
|
+
def pack(self, uuid: str) -> NexusPack: ...
|
|
48
|
+
|
|
49
|
+
def licence(self, pack: NexusPack) -> Path: ...
|
|
50
|
+
|
|
51
|
+
def download(
|
|
52
|
+
self,
|
|
53
|
+
pack: NexusPack,
|
|
54
|
+
*,
|
|
55
|
+
version: int,
|
|
56
|
+
file_format: str,
|
|
57
|
+
accept_licence: bool,
|
|
58
|
+
) -> Downloaded: ...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class Terms:
|
|
63
|
+
"""What a download answers while a pack's terms are not accepted: the licence file to read and
|
|
64
|
+
the citation to carry."""
|
|
65
|
+
|
|
66
|
+
licence: Path
|
|
67
|
+
citation: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class Downloads:
|
|
72
|
+
"""The files a download fetched, the pack's licence file, and the release's citation."""
|
|
73
|
+
|
|
74
|
+
inventory: Path
|
|
75
|
+
results: Path | None
|
|
76
|
+
licence: Path
|
|
77
|
+
citation: str
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@contextmanager
|
|
81
|
+
def translating() -> Iterator[None]:
|
|
82
|
+
"""A client call whose errors leave as the core's, of the kind and with the names the spec's
|
|
83
|
+
*Errors* table gives them."""
|
|
84
|
+
try:
|
|
85
|
+
yield
|
|
86
|
+
except LcaDataInterfaceError as error:
|
|
87
|
+
raise _translated(error) from error
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def download(
|
|
91
|
+
profile: Profile, session: Nexus, *, accept_terms: bool, results: bool
|
|
92
|
+
) -> Downloads | Terms:
|
|
93
|
+
"""Fetch a release's packs through Nexus, each file's SHA-256 compared with the profile's.
|
|
94
|
+
|
|
95
|
+
Without `accept_terms` the pack's licence file is fetched and nothing is downloaded: the answer
|
|
96
|
+
is the file a person reads to accept the terms. `results` also fetches the LCIA results pack,
|
|
97
|
+
which only a validation reads.
|
|
98
|
+
"""
|
|
99
|
+
with translating():
|
|
100
|
+
inventory_pack = session.pack(profile.inventory.pack)
|
|
101
|
+
licence = session.licence(inventory_pack)
|
|
102
|
+
if not accept_terms:
|
|
103
|
+
return Terms(licence=licence, citation=profile.citation)
|
|
104
|
+
inventory = session.download(
|
|
105
|
+
inventory_pack,
|
|
106
|
+
version=profile.inventory.version,
|
|
107
|
+
file_format=profile.inventory.format,
|
|
108
|
+
accept_licence=True,
|
|
109
|
+
)
|
|
110
|
+
results_file = _fetched(profile.results, session) if results else None
|
|
111
|
+
return Downloads(
|
|
112
|
+
inventory=_checked(profile.inventory, inventory),
|
|
113
|
+
results=None if results_file is None else _checked(profile.results, results_file),
|
|
114
|
+
licence=licence,
|
|
115
|
+
citation=profile.citation,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _fetched(pinned: Pack, session: Nexus) -> Downloaded:
|
|
120
|
+
"""One pack's file at the version and the format the profile pins, its terms accepted."""
|
|
121
|
+
return session.download(
|
|
122
|
+
session.pack(pinned.pack),
|
|
123
|
+
version=pinned.version,
|
|
124
|
+
file_format=pinned.format,
|
|
125
|
+
accept_licence=True,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _checked(pinned: Pack, downloaded: Downloaded) -> Path:
|
|
130
|
+
"""The path of a file whose SHA-256 is the one the profile pins; a different hash is a conflict
|
|
131
|
+
naming the file and both hashes."""
|
|
132
|
+
if downloaded.sha256 != pinned.sha256:
|
|
133
|
+
raise Conflict(
|
|
134
|
+
f"{downloaded.path.name} hashes to {downloaded.sha256}, the profile pins "
|
|
135
|
+
f"{pinned.sha256}"
|
|
136
|
+
)
|
|
137
|
+
return downloaded.path
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _translated(error: LcaDataInterfaceError) -> EcoSemanticError:
|
|
141
|
+
"""The core's error for one the client raised."""
|
|
142
|
+
return _TRANSLATIONS[type(error)](str(error))
|