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 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
+ [![PyPI version](https://badge.fury.io/py/enpax.svg)](https://badge.fury.io/py/enpax)
41
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/enpax)
42
+ [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](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
+ [![PyPI version](https://badge.fury.io/py/enpax.svg)](https://badge.fury.io/py/enpax)
4
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/enpax)
5
+ [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](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