enpax 0.1.0__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.
- enpax-0.1.0/LICENSE +29 -0
- enpax-0.1.0/PKG-INFO +155 -0
- enpax-0.1.0/README.md +118 -0
- enpax-0.1.0/enpax/__init__.py +0 -0
- enpax-0.1.0/enpax/base_model.py +93 -0
- enpax-0.1.0/enpax/models/__init__.py +0 -0
- enpax-0.1.0/enpax/models/bess_2025.py +617 -0
- enpax-0.1.0/enpax/models/generic_passthrough.py +172 -0
- enpax-0.1.0/enpax/models/solar_bess_2024Q1.py +832 -0
- enpax-0.1.0/enpax/outputs.py +78 -0
- enpax-0.1.0/enpax/registry.py +14 -0
- enpax-0.1.0/enpax/runner.py +34 -0
- enpax-0.1.0/enpax.egg-info/PKG-INFO +155 -0
- enpax-0.1.0/enpax.egg-info/SOURCES.txt +17 -0
- enpax-0.1.0/enpax.egg-info/dependency_links.txt +1 -0
- enpax-0.1.0/enpax.egg-info/requires.txt +17 -0
- enpax-0.1.0/enpax.egg-info/top_level.txt +1 -0
- enpax-0.1.0/pyproject.toml +63 -0
- enpax-0.1.0/setup.cfg +4 -0
enpax-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Alliance for Energy Innovation, LLC
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
enpax-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: enpax
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ENergy Platform — Automated eXpenditures
|
|
5
|
+
Author-email: Daniel Mulas Hernando <daniel.mulashernando@nlr.gov>, Kaitlin Brunik <kaitlin.brunik@nlr.gov>, Elenya Grant <elenya.grant@nlr.gov>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/NatLabRockies/ENPAX
|
|
8
|
+
Project-URL: Repository, https://github.com/NatLabRockies/ENPAX
|
|
9
|
+
Project-URL: Issues, https://github.com/NatLabRockies/ENPAX/issues
|
|
10
|
+
Keywords: energy,cost modeling,techno-economic analysis,solar,BESS,hybrid energy systems
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: numpy
|
|
23
|
+
Requires-Dist: pandas
|
|
24
|
+
Requires-Dist: pyyaml
|
|
25
|
+
Provides-Extra: develop
|
|
26
|
+
Requires-Dist: pytest; extra == "develop"
|
|
27
|
+
Requires-Dist: pytest-cov; extra == "develop"
|
|
28
|
+
Provides-Extra: examples
|
|
29
|
+
Requires-Dist: jupyterlab; extra == "examples"
|
|
30
|
+
Requires-Dist: ipykernel; extra == "examples"
|
|
31
|
+
Provides-Extra: all
|
|
32
|
+
Requires-Dist: pytest; extra == "all"
|
|
33
|
+
Requires-Dist: pytest-cov; extra == "all"
|
|
34
|
+
Requires-Dist: jupyterlab; extra == "all"
|
|
35
|
+
Requires-Dist: ipykernel; extra == "all"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# ENPAX
|
|
39
|
+
|
|
40
|
+
[](https://badge.fury.io/py/enpax)
|
|
41
|
+

|
|
42
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
43
|
+
|
|
44
|
+
ENPAX (**EN**ergy **P**latform — **A**utomated e**X**penditures) is an open-source Python library that provides modular, bottom-up capital and operating expenditure (CAPEX/OPEX) models for energy technologies. Cost estimates are derived using system design, characteristics, site specific details, power system considerations and infrastructure assumptions.
|
|
45
|
+
|
|
46
|
+
## Software Requirements
|
|
47
|
+
|
|
48
|
+
- Python 3.10+
|
|
49
|
+
## Installing ENPAX
|
|
50
|
+
|
|
51
|
+
### PyPI
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install enpax
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Source Installation
|
|
58
|
+
|
|
59
|
+
1. Using Git, navigate to a local target directory and clone the repository:
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/NatLabRockies/ENPAX.git
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
2. Navigate to `ENPAX`:
|
|
65
|
+
```bash
|
|
66
|
+
cd ENPAX
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
3. Create a new virtual environment and activate it. Using Conda and naming it `enpax`:
|
|
70
|
+
```bash
|
|
71
|
+
conda create --name enpax python=3.11 -y
|
|
72
|
+
conda activate enpax
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
4. Install ENPAX and its dependencies:
|
|
76
|
+
- To use ENPAX only:
|
|
77
|
+
```bash
|
|
78
|
+
pip install .
|
|
79
|
+
```
|
|
80
|
+
- To install in editable mode with development dependencies:
|
|
81
|
+
```bash
|
|
82
|
+
pip install -e ".[develop]"
|
|
83
|
+
```
|
|
84
|
+
- To install with example/notebook dependencies:
|
|
85
|
+
```bash
|
|
86
|
+
pip install -e ".[examples]"
|
|
87
|
+
```
|
|
88
|
+
- To install all optional dependencies at once:
|
|
89
|
+
```bash
|
|
90
|
+
pip install -e ".[all]"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Usage
|
|
94
|
+
|
|
95
|
+
ENPAX models can be configured and run either via YAML config files or Python dictionaries. Example notebooks are provided in the `examples/` directory:
|
|
96
|
+
|
|
97
|
+
- `examples_using_yaml_config_files.ipynb` — configure and run models using `.yaml` files from the `configs/` directory
|
|
98
|
+
- `examples_using_dictionaries.ipynb` — configure and run models programmatically in Python
|
|
99
|
+
### Quick Start
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from enpax.runner import CentralRunner
|
|
103
|
+
|
|
104
|
+
runner = CentralRunner(config="configs/solar_bess.yaml")
|
|
105
|
+
results = runner.run()
|
|
106
|
+
print(results)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Available Models
|
|
110
|
+
|
|
111
|
+
The library is structured around a shared abstract base class (BaseCostModel) that enforces a consistent interface — run_capex(), run_opex(), and run_design() — across all technology models, and a central runner (CentralRunner) that assembles multi-technology systems from YAML configuration files and returns structured output objects (TechResult, SystemResult). The current release includes two fully detailed technology models as well as the ability for a user to define their own technology cost model, both applicable for CAPEX and O&M cost estimations.
|
|
112
|
+
|
|
113
|
+
| Model | Description |
|
|
114
|
+
|-------|-------------|
|
|
115
|
+
| `solar_bess_2024Q1` | Solar PV + BESS hybrid CAPEX/OPEX model |
|
|
116
|
+
| `bess_2025` | Standalone battery energy storage CAPEX/OPEX model |
|
|
117
|
+
| `generic_passthrough` | Generic passthrough model for custom CAPEX/OPEX inputs |
|
|
118
|
+
|
|
119
|
+
## Release Notes
|
|
120
|
+
|
|
121
|
+
1. Ensure all tests pass.
|
|
122
|
+
2. Ensure this README is up to date.
|
|
123
|
+
3. Ensure dependency and Python versions are current.
|
|
124
|
+
4. Ensure `CHANGELOG.md` is up to date.
|
|
125
|
+
5. Bump the version in `pyproject.toml` using semantic versioning (<https://semver.org/>).
|
|
126
|
+
6. Make a pull request into `main` from `develop` or a patch release branch.
|
|
127
|
+
1. Merge `main` back into `develop` if `develop` was not the base branch.
|
|
128
|
+
7. Tag the new release and push it:
|
|
129
|
+
```bash
|
|
130
|
+
git tag -a v0.1.0 -m "message for v0.1.0"
|
|
131
|
+
git push origin v0.1.0
|
|
132
|
+
```
|
|
133
|
+
1. This will trigger the **Deploy to Test PyPI** GitHub Action. If it passes, proceed to step 8. If it fails, continue below.
|
|
134
|
+
2. Delete the tag locally and on remote:
|
|
135
|
+
```bash
|
|
136
|
+
git tag -d v0.1.0
|
|
137
|
+
git push --delete origin v0.1.0
|
|
138
|
+
```
|
|
139
|
+
3. Create a new branch off `main`, fix the build issue, and return to step 5.
|
|
140
|
+
8. Create a new release at <https://github.com/NatLabRockies/ENPAX/releases>, ensuring that:
|
|
141
|
+
1. The newly created tag is selected, and
|
|
142
|
+
2. **Generate release notes** is selected.
|
|
143
|
+
This will trigger the **Deploy to PyPI** GitHub Action.
|
|
144
|
+
## Contributing
|
|
145
|
+
|
|
146
|
+
Contributions are welcome. Please open an issue or submit a pull request against the `develop` branch.
|
|
147
|
+
|
|
148
|
+
## Authors
|
|
149
|
+
|
|
150
|
+
- [Daniel Mulas Hernando](daniel.mulashernando@nlr.gov) — National Laboratory of the Rockies
|
|
151
|
+
- [Kaitlin Brunik](kaitlin.brunik@nlr.gov) — National Laboratory of the Rockies
|
|
152
|
+
- [Elenya Grant](elenya.grant@nlr.gov) — National Laboratory of the Rockies
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
This project is licensed under the BSD 3-Clause License. See [LICENSE](LICENSE) for details.
|
enpax-0.1.0/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# ENPAX
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/enpax)
|
|
4
|
+

|
|
5
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
6
|
+
|
|
7
|
+
ENPAX (**EN**ergy **P**latform — **A**utomated e**X**penditures) is an open-source Python library that provides modular, bottom-up capital and operating expenditure (CAPEX/OPEX) models for energy technologies. Cost estimates are derived using system design, characteristics, site specific details, power system considerations and infrastructure assumptions.
|
|
8
|
+
|
|
9
|
+
## Software Requirements
|
|
10
|
+
|
|
11
|
+
- Python 3.10+
|
|
12
|
+
## Installing ENPAX
|
|
13
|
+
|
|
14
|
+
### PyPI
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install enpax
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Source Installation
|
|
21
|
+
|
|
22
|
+
1. Using Git, navigate to a local target directory and clone the repository:
|
|
23
|
+
```bash
|
|
24
|
+
git clone https://github.com/NatLabRockies/ENPAX.git
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. Navigate to `ENPAX`:
|
|
28
|
+
```bash
|
|
29
|
+
cd ENPAX
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. Create a new virtual environment and activate it. Using Conda and naming it `enpax`:
|
|
33
|
+
```bash
|
|
34
|
+
conda create --name enpax python=3.11 -y
|
|
35
|
+
conda activate enpax
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
4. Install ENPAX and its dependencies:
|
|
39
|
+
- To use ENPAX only:
|
|
40
|
+
```bash
|
|
41
|
+
pip install .
|
|
42
|
+
```
|
|
43
|
+
- To install in editable mode with development dependencies:
|
|
44
|
+
```bash
|
|
45
|
+
pip install -e ".[develop]"
|
|
46
|
+
```
|
|
47
|
+
- To install with example/notebook dependencies:
|
|
48
|
+
```bash
|
|
49
|
+
pip install -e ".[examples]"
|
|
50
|
+
```
|
|
51
|
+
- To install all optional dependencies at once:
|
|
52
|
+
```bash
|
|
53
|
+
pip install -e ".[all]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
ENPAX models can be configured and run either via YAML config files or Python dictionaries. Example notebooks are provided in the `examples/` directory:
|
|
59
|
+
|
|
60
|
+
- `examples_using_yaml_config_files.ipynb` — configure and run models using `.yaml` files from the `configs/` directory
|
|
61
|
+
- `examples_using_dictionaries.ipynb` — configure and run models programmatically in Python
|
|
62
|
+
### Quick Start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from enpax.runner import CentralRunner
|
|
66
|
+
|
|
67
|
+
runner = CentralRunner(config="configs/solar_bess.yaml")
|
|
68
|
+
results = runner.run()
|
|
69
|
+
print(results)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Available Models
|
|
73
|
+
|
|
74
|
+
The library is structured around a shared abstract base class (BaseCostModel) that enforces a consistent interface — run_capex(), run_opex(), and run_design() — across all technology models, and a central runner (CentralRunner) that assembles multi-technology systems from YAML configuration files and returns structured output objects (TechResult, SystemResult). The current release includes two fully detailed technology models as well as the ability for a user to define their own technology cost model, both applicable for CAPEX and O&M cost estimations.
|
|
75
|
+
|
|
76
|
+
| Model | Description |
|
|
77
|
+
|-------|-------------|
|
|
78
|
+
| `solar_bess_2024Q1` | Solar PV + BESS hybrid CAPEX/OPEX model |
|
|
79
|
+
| `bess_2025` | Standalone battery energy storage CAPEX/OPEX model |
|
|
80
|
+
| `generic_passthrough` | Generic passthrough model for custom CAPEX/OPEX inputs |
|
|
81
|
+
|
|
82
|
+
## Release Notes
|
|
83
|
+
|
|
84
|
+
1. Ensure all tests pass.
|
|
85
|
+
2. Ensure this README is up to date.
|
|
86
|
+
3. Ensure dependency and Python versions are current.
|
|
87
|
+
4. Ensure `CHANGELOG.md` is up to date.
|
|
88
|
+
5. Bump the version in `pyproject.toml` using semantic versioning (<https://semver.org/>).
|
|
89
|
+
6. Make a pull request into `main` from `develop` or a patch release branch.
|
|
90
|
+
1. Merge `main` back into `develop` if `develop` was not the base branch.
|
|
91
|
+
7. Tag the new release and push it:
|
|
92
|
+
```bash
|
|
93
|
+
git tag -a v0.1.0 -m "message for v0.1.0"
|
|
94
|
+
git push origin v0.1.0
|
|
95
|
+
```
|
|
96
|
+
1. This will trigger the **Deploy to Test PyPI** GitHub Action. If it passes, proceed to step 8. If it fails, continue below.
|
|
97
|
+
2. Delete the tag locally and on remote:
|
|
98
|
+
```bash
|
|
99
|
+
git tag -d v0.1.0
|
|
100
|
+
git push --delete origin v0.1.0
|
|
101
|
+
```
|
|
102
|
+
3. Create a new branch off `main`, fix the build issue, and return to step 5.
|
|
103
|
+
8. Create a new release at <https://github.com/NatLabRockies/ENPAX/releases>, ensuring that:
|
|
104
|
+
1. The newly created tag is selected, and
|
|
105
|
+
2. **Generate release notes** is selected.
|
|
106
|
+
This will trigger the **Deploy to PyPI** GitHub Action.
|
|
107
|
+
## Contributing
|
|
108
|
+
|
|
109
|
+
Contributions are welcome. Please open an issue or submit a pull request against the `develop` branch.
|
|
110
|
+
|
|
111
|
+
## Authors
|
|
112
|
+
|
|
113
|
+
- [Daniel Mulas Hernando](daniel.mulashernando@nlr.gov) — National Laboratory of the Rockies
|
|
114
|
+
- [Kaitlin Brunik](kaitlin.brunik@nlr.gov) — National Laboratory of the Rockies
|
|
115
|
+
- [Elenya Grant](elenya.grant@nlr.gov) — National Laboratory of the Rockies
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
This project is licensed under the BSD 3-Clause License. See [LICENSE](LICENSE) for details.
|
|
File without changes
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import json
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
from enpax.outputs import DesignSummary, CapexBreakdown, OpexBreakdown, TechResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseCostModel(ABC):
|
|
11
|
+
|
|
12
|
+
# Subclasses declare their defaults dict at class level
|
|
13
|
+
DEFAULTS: dict = {}
|
|
14
|
+
|
|
15
|
+
def __init__(self, name: str, tech_type: str, params: dict):
|
|
16
|
+
self.name = name
|
|
17
|
+
self.tech_type = tech_type
|
|
18
|
+
if params and not isinstance(params, dict):
|
|
19
|
+
raise ValueError("params must be a dictionary.")
|
|
20
|
+
self.config = {**self.DEFAULTS, **(params or {})}
|
|
21
|
+
|
|
22
|
+
# ------------------------------------------------------------------
|
|
23
|
+
# Config loading — shared by all subclasses, no duplication needed
|
|
24
|
+
# ------------------------------------------------------------------
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_config_file(cls, file_name: str, name: str = None, tech_type: str = None):
|
|
27
|
+
base_dir = Path(__file__).parent.absolute()
|
|
28
|
+
search_paths = [
|
|
29
|
+
base_dir / file_name,
|
|
30
|
+
base_dir / "configs" / file_name,
|
|
31
|
+
base_dir.parent / "configs" / file_name,
|
|
32
|
+
base_dir.parent / "configs" / "single_tech" / file_name,
|
|
33
|
+
]
|
|
34
|
+
full_path = next((p for p in search_paths if p.exists()), Path(file_name))
|
|
35
|
+
|
|
36
|
+
if not full_path.exists():
|
|
37
|
+
tried = "\n".join(str(p) for p in search_paths)
|
|
38
|
+
raise FileNotFoundError(f"Could not find '{file_name}'. Checked:\n{tried}")
|
|
39
|
+
|
|
40
|
+
with open(full_path) as f:
|
|
41
|
+
if full_path.suffix == ".json":
|
|
42
|
+
data = json.load(f)
|
|
43
|
+
elif full_path.suffix in (".yaml", ".yml"):
|
|
44
|
+
data = yaml.safe_load(f)
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError(f"Unsupported format: {full_path.suffix}. Use .json or .yaml")
|
|
47
|
+
|
|
48
|
+
resolved_name = name or data.pop("name", cls.__name__)
|
|
49
|
+
resolved_type = tech_type or data.pop("tech_type", cls.__name__.lower())
|
|
50
|
+
return cls(name=resolved_name, tech_type=resolved_type, params=data)
|
|
51
|
+
|
|
52
|
+
# ------------------------------------------------------------------
|
|
53
|
+
# Abstract interface every tech model must implement
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def run_capex(self) -> CapexBreakdown:
|
|
57
|
+
"""Return a CapexBreakdown with total, unit, and line_items."""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def run_opex(self) -> Optional[OpexBreakdown]:
|
|
61
|
+
"""
|
|
62
|
+
Override in subclass if this tech has OPEX.
|
|
63
|
+
Returns None by default — runner skips gracefully.
|
|
64
|
+
"""
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
# Dynamic attribute resolution — preserves existing __getattr__ pattern
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
def __getattr__(self, name: str):
|
|
71
|
+
if name.startswith("calculate_") and "_per_" in name:
|
|
72
|
+
# e.g. calculate_li_ion_cost_per_kwh → get_li_ion_cost_breakdown
|
|
73
|
+
suffix_start = name.rfind("_per_")
|
|
74
|
+
getter_name = "get_" + name[10:suffix_start] + "_breakdown"
|
|
75
|
+
if hasattr(self.__class__, getter_name) or getter_name in self.__dict__:
|
|
76
|
+
def _caller():
|
|
77
|
+
breakdown = getattr(self, getter_name)()
|
|
78
|
+
return breakdown[next(k for k in breakdown if k.startswith("total_"))]
|
|
79
|
+
return _caller
|
|
80
|
+
raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
|
|
81
|
+
|
|
82
|
+
# ------------------------------------------------------------------
|
|
83
|
+
# Top-level runner — produces a TechResult
|
|
84
|
+
# ------------------------------------------------------------------
|
|
85
|
+
def run(self) -> TechResult:
|
|
86
|
+
return TechResult(
|
|
87
|
+
tech_name=self.name,
|
|
88
|
+
tech_type=self.tech_type,
|
|
89
|
+
capex=self.run_capex(),
|
|
90
|
+
opex=self.run_opex(),
|
|
91
|
+
design=self.run_design(),
|
|
92
|
+
|
|
93
|
+
)
|
|
File without changes
|