OpenPyTEA 2.0.0__tar.gz → 2.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.
Files changed (27) hide show
  1. {openpytea-2.0.0/src/OpenPyTEA.egg-info → openpytea-2.1.0}/PKG-INFO +59 -4
  2. {openpytea-2.0.0 → openpytea-2.1.0}/README.md +58 -3
  3. {openpytea-2.0.0 → openpytea-2.1.0}/pyproject.toml +1 -1
  4. {openpytea-2.0.0 → openpytea-2.1.0/src/OpenPyTEA.egg-info}/PKG-INFO +59 -4
  5. {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/analysis.py +133 -61
  6. {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/equipment.py +74 -27
  7. {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/plant.py +386 -43
  8. openpytea-2.1.0/tests/test_analysis.py +113 -0
  9. openpytea-2.1.0/tests/test_plant.py +119 -0
  10. openpytea-2.0.0/tests/test_analysis.py +0 -98
  11. openpytea-2.0.0/tests/test_plant.py +0 -36
  12. {openpytea-2.0.0 → openpytea-2.1.0}/LICENSE +0 -0
  13. {openpytea-2.0.0 → openpytea-2.1.0}/setup.cfg +0 -0
  14. {openpytea-2.0.0 → openpytea-2.1.0}/src/OpenPyTEA.egg-info/SOURCES.txt +0 -0
  15. {openpytea-2.0.0 → openpytea-2.1.0}/src/OpenPyTEA.egg-info/dependency_links.txt +0 -0
  16. {openpytea-2.0.0 → openpytea-2.1.0}/src/OpenPyTEA.egg-info/requires.txt +0 -0
  17. {openpytea-2.0.0 → openpytea-2.1.0}/src/OpenPyTEA.egg-info/top_level.txt +0 -0
  18. {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/__init__.py +0 -0
  19. {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/data/cepci_values.csv +0 -0
  20. {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/data/cost_correlations.csv +0 -0
  21. {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/helpers.py +0 -0
  22. {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/io.py +0 -0
  23. {openpytea-2.0.0 → openpytea-2.1.0}/src/openpytea/plotting.py +0 -0
  24. {openpytea-2.0.0 → openpytea-2.1.0}/tests/test_equipment.py +0 -0
  25. {openpytea-2.0.0 → openpytea-2.1.0}/tests/test_plotting.py +0 -0
  26. {openpytea-2.0.0 → openpytea-2.1.0}/tests/test_run_tea.py +0 -0
  27. {openpytea-2.0.0 → openpytea-2.1.0}/tests/test_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenPyTEA
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: OpenPyTEA: An open-source Python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation
5
5
  Maintainer-email: "Panji B. Tamarona" <P.B.Tamarona@tudelft.nl>
6
6
  License-Expression: MIT
@@ -97,11 +97,30 @@ src/openpytea/
97
97
  ├── helpers.py # Helper functions for data handling and common operations
98
98
  └── data/ # Cost correlations database and CEPCI data
99
99
  examples/ # Example notebooks and case studies
100
- walkthrough.ipynb # walkthrough of the package
100
+ walkthrough.ipynb # Walkthrough of the package
101
+
102
+ backend/ # FastAPI backend for the web GUI
103
+ ├── app/
104
+ │ ├── main.py # FastAPI app with CORS and router mounting
105
+ │ ├── state.py # In-memory session state
106
+ │ ├── schemas.py # Pydantic request/response models
107
+ │ ├── util.py # JSON serialization utilities
108
+ │ ├── routers/ # API endpoints (equipment, plant, analysis, I/O)
109
+ │ └── presets/ # Example preset JSON files
110
+ └── requirements.txt
111
+
112
+ frontend/ # React + TypeScript web GUI
113
+ ├── src/
114
+ │ ├── api/client.ts # Typed API client
115
+ │ ├── types/index.ts # TypeScript interfaces
116
+ │ ├── pages/ # Equipment, Plant Config, Results, Analysis, Monte Carlo, Compare
117
+ │ ├── App.tsx # Tab navigation + examples dropdown
118
+ │ └── App.css # Styling
119
+ └── package.json
120
+
101
121
  pyproject.toml
102
122
  README.md
103
123
  ```
104
-
105
124
  ---
106
125
 
107
126
  ## 🏗️ Software Architecture
@@ -112,6 +131,42 @@ Software architecture and data flow of **OpenPyTEA**, illustrating the progressi
112
131
 
113
132
  ---
114
133
 
134
+ ## 🖥️ Web GUI (**work in progress**)
135
+
136
+ OpenPyTEA includes an optional web-based graphical interface for users who prefer a visual workflow over Python scripting. The GUI provides the full TEA workflow through a tabbed browser interface:
137
+
138
+ - **Equipment** — add, edit, and remove equipment with cost database lookup
139
+ - **Plant Config** — configure location, financial parameters, labor, products, and variable OPEX
140
+ - **Results** — run calculations and view metric cards, cost breakdown charts, and cash flow tables
141
+ - **Analysis** — sensitivity plots and tornado diagrams. Sensitivity supports a **multi-panel grid** (different parameter and metric per panel, e.g. NPV vs. interest rate, ROI vs. electricity price, all in one figure) and **multi-plant overlay** (curves for every plant added on the Compare tab share the same axes for direct comparison)
142
+ - **Monte Carlo** — uncertainty analysis with histogram distributions, fitted normal curves, and summary statistics. **Multi-plant overlay** shows distributions for several plants on the same axes, mirroring `plot_multiple_monte_carlo` from the library
143
+ - **Compare** — side-by-side comparison of saved plants (CAPEX/OPEX breakdown bars, key metric bars). Plants imported here are also reused as the overlay set on the Analysis and Monte Carlo tabs
144
+ - **Downloadable charts** — all plots include a download button to export as standalone PNG images with full axis labels
145
+ - **Examples** — built-in presets from the case study notebooks for quick demonstration
146
+
147
+ ### Running the GUI
148
+
149
+ **Backend** (requires Python 3.10+):
150
+ ```bash
151
+ pip install -e . # install OpenPyTEA from repo root
152
+ cd backend
153
+ pip install -r requirements.txt
154
+ PYTHONPATH=../src python3 -m uvicorn app.main:app --reload --port 8000
155
+ ```
156
+
157
+ **Frontend** (requires Node.js):
158
+ ```bash
159
+ cd frontend
160
+ npm install
161
+ npm run dev
162
+ ```
163
+
164
+ Then open http://localhost:5173. Click **Examples** in the header to load a case study preset and explore.
165
+
166
+ For detailed architecture documentation, see `GUI_ARCHITECTURE.md`.
167
+
168
+ ---
169
+
115
170
  ## 🧠 Core Concepts
116
171
 
117
172
  ### 1. **Equipment-level costing**
@@ -299,7 +354,7 @@ Or if you prefer to cite manually, you may use:
299
354
  title = {OpenPyTEA: An open-source python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation},
300
355
  year = {2025},
301
356
  url = {\url{https://github.com/pbtamarona/OpenPyTEA}},
302
- version = {1.2.0},
357
+ version = {2.0.0},
303
358
  note = {Accessed: YYYY-MM-DD}
304
359
  }
305
360
  ```
@@ -73,11 +73,30 @@ src/openpytea/
73
73
  ├── helpers.py # Helper functions for data handling and common operations
74
74
  └── data/ # Cost correlations database and CEPCI data
75
75
  examples/ # Example notebooks and case studies
76
- walkthrough.ipynb # walkthrough of the package
76
+ walkthrough.ipynb # Walkthrough of the package
77
+
78
+ backend/ # FastAPI backend for the web GUI
79
+ ├── app/
80
+ │ ├── main.py # FastAPI app with CORS and router mounting
81
+ │ ├── state.py # In-memory session state
82
+ │ ├── schemas.py # Pydantic request/response models
83
+ │ ├── util.py # JSON serialization utilities
84
+ │ ├── routers/ # API endpoints (equipment, plant, analysis, I/O)
85
+ │ └── presets/ # Example preset JSON files
86
+ └── requirements.txt
87
+
88
+ frontend/ # React + TypeScript web GUI
89
+ ├── src/
90
+ │ ├── api/client.ts # Typed API client
91
+ │ ├── types/index.ts # TypeScript interfaces
92
+ │ ├── pages/ # Equipment, Plant Config, Results, Analysis, Monte Carlo, Compare
93
+ │ ├── App.tsx # Tab navigation + examples dropdown
94
+ │ └── App.css # Styling
95
+ └── package.json
96
+
77
97
  pyproject.toml
78
98
  README.md
79
99
  ```
80
-
81
100
  ---
82
101
 
83
102
  ## 🏗️ Software Architecture
@@ -88,6 +107,42 @@ Software architecture and data flow of **OpenPyTEA**, illustrating the progressi
88
107
 
89
108
  ---
90
109
 
110
+ ## 🖥️ Web GUI (**work in progress**)
111
+
112
+ OpenPyTEA includes an optional web-based graphical interface for users who prefer a visual workflow over Python scripting. The GUI provides the full TEA workflow through a tabbed browser interface:
113
+
114
+ - **Equipment** — add, edit, and remove equipment with cost database lookup
115
+ - **Plant Config** — configure location, financial parameters, labor, products, and variable OPEX
116
+ - **Results** — run calculations and view metric cards, cost breakdown charts, and cash flow tables
117
+ - **Analysis** — sensitivity plots and tornado diagrams. Sensitivity supports a **multi-panel grid** (different parameter and metric per panel, e.g. NPV vs. interest rate, ROI vs. electricity price, all in one figure) and **multi-plant overlay** (curves for every plant added on the Compare tab share the same axes for direct comparison)
118
+ - **Monte Carlo** — uncertainty analysis with histogram distributions, fitted normal curves, and summary statistics. **Multi-plant overlay** shows distributions for several plants on the same axes, mirroring `plot_multiple_monte_carlo` from the library
119
+ - **Compare** — side-by-side comparison of saved plants (CAPEX/OPEX breakdown bars, key metric bars). Plants imported here are also reused as the overlay set on the Analysis and Monte Carlo tabs
120
+ - **Downloadable charts** — all plots include a download button to export as standalone PNG images with full axis labels
121
+ - **Examples** — built-in presets from the case study notebooks for quick demonstration
122
+
123
+ ### Running the GUI
124
+
125
+ **Backend** (requires Python 3.10+):
126
+ ```bash
127
+ pip install -e . # install OpenPyTEA from repo root
128
+ cd backend
129
+ pip install -r requirements.txt
130
+ PYTHONPATH=../src python3 -m uvicorn app.main:app --reload --port 8000
131
+ ```
132
+
133
+ **Frontend** (requires Node.js):
134
+ ```bash
135
+ cd frontend
136
+ npm install
137
+ npm run dev
138
+ ```
139
+
140
+ Then open http://localhost:5173. Click **Examples** in the header to load a case study preset and explore.
141
+
142
+ For detailed architecture documentation, see `GUI_ARCHITECTURE.md`.
143
+
144
+ ---
145
+
91
146
  ## 🧠 Core Concepts
92
147
 
93
148
  ### 1. **Equipment-level costing**
@@ -275,7 +330,7 @@ Or if you prefer to cite manually, you may use:
275
330
  title = {OpenPyTEA: An open-source python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation},
276
331
  year = {2025},
277
332
  url = {\url{https://github.com/pbtamarona/OpenPyTEA}},
278
- version = {1.2.0},
333
+ version = {2.0.0},
279
334
  note = {Accessed: YYYY-MM-DD}
280
335
  }
281
336
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "OpenPyTEA"
3
- version = "2.0.0"
3
+ version = "2.1.0"
4
4
  description = "OpenPyTEA: An open-source Python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: OpenPyTEA
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: OpenPyTEA: An open-source Python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation
5
5
  Maintainer-email: "Panji B. Tamarona" <P.B.Tamarona@tudelft.nl>
6
6
  License-Expression: MIT
@@ -97,11 +97,30 @@ src/openpytea/
97
97
  ├── helpers.py # Helper functions for data handling and common operations
98
98
  └── data/ # Cost correlations database and CEPCI data
99
99
  examples/ # Example notebooks and case studies
100
- walkthrough.ipynb # walkthrough of the package
100
+ walkthrough.ipynb # Walkthrough of the package
101
+
102
+ backend/ # FastAPI backend for the web GUI
103
+ ├── app/
104
+ │ ├── main.py # FastAPI app with CORS and router mounting
105
+ │ ├── state.py # In-memory session state
106
+ │ ├── schemas.py # Pydantic request/response models
107
+ │ ├── util.py # JSON serialization utilities
108
+ │ ├── routers/ # API endpoints (equipment, plant, analysis, I/O)
109
+ │ └── presets/ # Example preset JSON files
110
+ └── requirements.txt
111
+
112
+ frontend/ # React + TypeScript web GUI
113
+ ├── src/
114
+ │ ├── api/client.ts # Typed API client
115
+ │ ├── types/index.ts # TypeScript interfaces
116
+ │ ├── pages/ # Equipment, Plant Config, Results, Analysis, Monte Carlo, Compare
117
+ │ ├── App.tsx # Tab navigation + examples dropdown
118
+ │ └── App.css # Styling
119
+ └── package.json
120
+
101
121
  pyproject.toml
102
122
  README.md
103
123
  ```
104
-
105
124
  ---
106
125
 
107
126
  ## 🏗️ Software Architecture
@@ -112,6 +131,42 @@ Software architecture and data flow of **OpenPyTEA**, illustrating the progressi
112
131
 
113
132
  ---
114
133
 
134
+ ## 🖥️ Web GUI (**work in progress**)
135
+
136
+ OpenPyTEA includes an optional web-based graphical interface for users who prefer a visual workflow over Python scripting. The GUI provides the full TEA workflow through a tabbed browser interface:
137
+
138
+ - **Equipment** — add, edit, and remove equipment with cost database lookup
139
+ - **Plant Config** — configure location, financial parameters, labor, products, and variable OPEX
140
+ - **Results** — run calculations and view metric cards, cost breakdown charts, and cash flow tables
141
+ - **Analysis** — sensitivity plots and tornado diagrams. Sensitivity supports a **multi-panel grid** (different parameter and metric per panel, e.g. NPV vs. interest rate, ROI vs. electricity price, all in one figure) and **multi-plant overlay** (curves for every plant added on the Compare tab share the same axes for direct comparison)
142
+ - **Monte Carlo** — uncertainty analysis with histogram distributions, fitted normal curves, and summary statistics. **Multi-plant overlay** shows distributions for several plants on the same axes, mirroring `plot_multiple_monte_carlo` from the library
143
+ - **Compare** — side-by-side comparison of saved plants (CAPEX/OPEX breakdown bars, key metric bars). Plants imported here are also reused as the overlay set on the Analysis and Monte Carlo tabs
144
+ - **Downloadable charts** — all plots include a download button to export as standalone PNG images with full axis labels
145
+ - **Examples** — built-in presets from the case study notebooks for quick demonstration
146
+
147
+ ### Running the GUI
148
+
149
+ **Backend** (requires Python 3.10+):
150
+ ```bash
151
+ pip install -e . # install OpenPyTEA from repo root
152
+ cd backend
153
+ pip install -r requirements.txt
154
+ PYTHONPATH=../src python3 -m uvicorn app.main:app --reload --port 8000
155
+ ```
156
+
157
+ **Frontend** (requires Node.js):
158
+ ```bash
159
+ cd frontend
160
+ npm install
161
+ npm run dev
162
+ ```
163
+
164
+ Then open http://localhost:5173. Click **Examples** in the header to load a case study preset and explore.
165
+
166
+ For detailed architecture documentation, see `GUI_ARCHITECTURE.md`.
167
+
168
+ ---
169
+
115
170
  ## 🧠 Core Concepts
116
171
 
117
172
  ### 1. **Equipment-level costing**
@@ -299,7 +354,7 @@ Or if you prefer to cite manually, you may use:
299
354
  title = {OpenPyTEA: An open-source python toolkit for techno-economic assessment of process plants with economic sensitivity and uncertainty evaluation},
300
355
  year = {2025},
301
356
  url = {\url{https://github.com/pbtamarona/OpenPyTEA}},
302
- version = {1.2.0},
357
+ version = {2.0.0},
303
358
  note = {Accessed: YYYY-MM-DD}
304
359
  }
305
360
  ```
@@ -630,54 +630,76 @@ def monte_carlo(plant,
630
630
  batch_size: int = 1000,
631
631
  additional_capex: bool = False):
632
632
  """
633
- This function conducts a probabilistic analysis of a plant's economic
634
- performance by sampling from distributions of various input parameters
635
- and computing multiple economic metrics across the samples. The simulation
636
- processes samples in batches to optimize memory usage while maintaining
637
- computational efficiency.
638
- configuration attributes initialized. The plant will be deep copied
639
- internally to preserve the original state.
640
- Must be divisible by batch_size for optimal performance.
641
- Reduces memory usage by processing samples in chunks rather than
642
- all at once. Smaller batches use less memory but may be slower.
643
- calculations. Default is False. Only applies when product prices
644
- are available.
633
+ Probabilistic analysis of a plant's economic performance by sampling
634
+ input parameters from truncated normal distributions and computing
635
+ economic metrics across all samples. Samples are processed in batches
636
+ to manage memory.
637
+
638
+ Parameters
639
+ ----------
640
+ plant : Plant
641
+ A fully configured Plant instance. Baseline economic calculations
642
+ are run internally before sampling begins. The original plant is not
643
+ modified; results are stored on it after the simulation completes.
644
+ num_samples : int, optional
645
+ Total number of Monte Carlo samples. Default is 1_000_000.
646
+ batch_size : int, optional
647
+ Number of samples processed per batch. Smaller values reduce peak
648
+ memory at the cost of slightly more overhead. Default is 1000.
649
+ additional_capex : bool, optional
650
+ Include additional CAPEX in ROI and payback time calculations.
651
+ Only applies when product prices are available. Default is False.
652
+
653
+ Returns
654
+ -------
645
655
  dict
646
- A dictionary containing the Monte Carlo simulation results with keys:
647
- - "name" : str
648
- Name of the analyzed plant.
649
- - "metrics" : dict
650
- (only populated if product prices available).
651
- (only populated if product prices available).
652
- (only populated if product prices available).
653
- - "inputs" : dict
654
- - Variable opex price samples for each item formatted as
655
- "{Item} price"
656
- - Product price samples for each product formatted as
657
- "{Product} product price"
658
- - "num_samples" : int
659
- Number of samples generated in the simulation.
660
- - "additional_capex" : bool
661
- Whether additional CAPEX was included in calculations.
662
- - "currency" : str
663
- Currency symbol used in the plant's economic calculations.
664
- - The plant object is deep copied to avoid modifying the original during
665
- sampling. Results are stored back to the original plant as
666
- - All input parameters are sampled from truncated normal distributions
667
- with parameter-specific mean, standard deviation, and bounds.
668
- are defined in the plant configuration.
669
- - Progress is displayed via a progress bar showing batch processing status.
670
- - Fixed capital and fixed opex factors use a standard distribution with
671
- mean=1, std=0.3, bounds=[0.25, 1.75].
672
- - Operator hourly rate is sampled from configurable
673
- distribution parameters.
674
- - Project lifetime and interest rate use 2-sigma bounds around baseline
675
- values.
656
+ A dictionary with the following keys:
657
+
658
+ - ``"name"`` : str plant name.
659
+ - ``"metrics"`` : dict — arrays of length *num_samples*:
660
+ - ``"LCOP"`` levelized cost of production (always populated).
661
+ - ``"NPV"`` net present value (requires product prices).
662
+ - ``"ROI"`` return on investment (requires product prices).
663
+ - ``"PBT"`` payback time (requires product prices).
664
+ - ``"inputs"`` : dict sampled input arrays, always containing:
665
+ - ``"Fixed capital factor"``
666
+ - ``"Fixed opex factor"``
667
+ - ``"Operator hourly rate"``
668
+ - ``"Project lifetime"``
669
+ - ``"Interest rate"``
670
+ - ``"{Item} price"`` for each variable OPEX item.
671
+ - ``"{Product} product price"`` for each product.
672
+ And conditionally (when ``std > 0`` in ``project_uncertainties``):
673
+ - ``"Plant utilization"``
674
+ - ``"Tax rate"``
675
+ - ``"num_samples"`` : int number of samples generated.
676
+ - ``"additional_capex"`` : bool whether additional CAPEX was
677
+ included.
678
+ - ``"currency"`` : str currency symbol.
679
+
680
+ Notes
681
+ -----
682
+ - Sampling distributions for fixed capital factor, fixed opex factor,
683
+ project lifetime, interest rate, plant utilization, and tax rate are
684
+ controlled by the plant's ``project_uncertainties`` configuration dict
685
+ (see Plant class docstring). Default std, min, and max values are used
686
+ when a parameter is absent from that dict.
687
+ - ``plant_utilization`` and ``tax_rate`` have a default ``std`` of 0 and
688
+ are only sampled when explicitly set to a positive value in
689
+ ``project_uncertainties``.
690
+ - Variable OPEX items and products are sampled using the ``std``,
691
+ ``min``, and ``max`` fields defined within each item's own config dict.
692
+ - The plant is deep-copied each batch to avoid mutating the original.
693
+ After the run, ``monte_carlo_metrics`` and ``monte_carlo_inputs`` are
694
+ written back to the original plant.
695
+ - Progress is shown via a tqdm progress bar over batches.
696
+
676
697
  Raises
677
698
  ------
678
699
  AttributeError
679
700
  If the plant object lacks required economic calculation methods or
680
701
  configuration attributes.
702
+
681
703
  Examples
682
704
  --------
683
705
  >>> results = monte_carlo(plant, num_samples=10000, batch_size=500)
@@ -702,7 +724,58 @@ def monte_carlo(plant,
702
724
  "PBT": np.zeros(num_samples),
703
725
  }
704
726
 
705
- # ---- Allocate all input distributions (same as before) ----
727
+ # ---- Resolve project uncertainty parameters ----
728
+ pu = plant.project_uncertainties
729
+
730
+ fc_cfg = pu.get("fixed_capital_factor", {})
731
+ fc_std = fc_cfg.get("std", 0.3)
732
+ fc_min = fc_cfg.get("min", 0.25)
733
+ fc_max = fc_cfg.get("max", 1.75)
734
+
735
+ fo_cfg = pu.get("fixed_opex_factor", {})
736
+ fo_std = fo_cfg.get("std", 0.3)
737
+ fo_min = fo_cfg.get("min", 0.25)
738
+ fo_max = fo_cfg.get("max", 1.75)
739
+
740
+ lt_cfg = pu.get("project_lifetime", {})
741
+ lt_std = lt_cfg.get("std", 5)
742
+ lt_min = lt_cfg.get("min", max(5, plant.project_lifetime - 2 * lt_std))
743
+ lt_max = lt_cfg.get("max", plant.project_lifetime + 2 * lt_std)
744
+
745
+ ir_cfg = pu.get("interest_rate", {})
746
+ ir_std = ir_cfg.get("std", 0.03)
747
+ ir_min = ir_cfg.get("min", max(0.02, plant.interest_rate - 2 * ir_std))
748
+ ir_max = ir_cfg.get("max", plant.interest_rate + 2 * ir_std)
749
+
750
+ pu_util_cfg = pu.get("plant_utilization", {})
751
+ pu_util_std = pu_util_cfg.get("std", 0)
752
+ if pu_util_std > 0:
753
+ pu_util_mean = plant.plant_utilization
754
+ pu_util_min = pu_util_cfg.get(
755
+ "min", max(0.0, pu_util_mean - 2 * pu_util_std)
756
+ )
757
+ pu_util_max = pu_util_cfg.get(
758
+ "max", min(1.0, pu_util_mean + 2 * pu_util_std)
759
+ )
760
+ plant_utilizations = _truncated_normal_samples(
761
+ pu_util_mean, pu_util_std, pu_util_min, pu_util_max, num_samples
762
+ )
763
+ else:
764
+ plant_utilizations = None
765
+
766
+ tr_cfg = pu.get("tax_rate", {})
767
+ tr_std = tr_cfg.get("std", 0)
768
+ if tr_std > 0:
769
+ tr_mean = plant.tax_rate
770
+ tr_min = tr_cfg.get("min", max(0.0, tr_mean - 2 * tr_std))
771
+ tr_max = tr_cfg.get("max", min(1.0, tr_mean + 2 * tr_std))
772
+ tax_rates = _truncated_normal_samples(
773
+ tr_mean, tr_std, tr_min, tr_max, num_samples
774
+ )
775
+ else:
776
+ tax_rates = None
777
+
778
+ # ---- Allocate all input distributions ----
706
779
  op_cfg = plant.operator_hourly_rate
707
780
  op_mean = op_cfg.get("rate", 38.11)
708
781
  op_std = op_cfg.get("std", 20 / 2)
@@ -711,11 +784,11 @@ def monte_carlo(plant,
711
784
 
712
785
  # ---- Sample ALL inputs once ----
713
786
  fixed_capitals = _truncated_normal_samples(
714
- 1, 0.3, 0.25, 1.75, num_samples
787
+ 1, fc_std, fc_min, fc_max, num_samples
715
788
  )
716
789
 
717
790
  fixed_opexs = _truncated_normal_samples(
718
- 1, 0.3, 0.25, 1.75, num_samples
791
+ 1, fo_std, fo_min, fo_max, num_samples
719
792
  )
720
793
 
721
794
  operator_hourlys = _truncated_normal_samples(
@@ -723,19 +796,11 @@ def monte_carlo(plant,
723
796
  )
724
797
 
725
798
  project_lifetimes = _truncated_normal_samples(
726
- plant.project_lifetime,
727
- 5,
728
- max(5, plant.project_lifetime - 2 * 5),
729
- plant.project_lifetime + 2 * 5,
730
- num_samples,
799
+ plant.project_lifetime, lt_std, lt_min, lt_max, num_samples,
731
800
  )
732
801
 
733
802
  interests = _truncated_normal_samples(
734
- plant.interest_rate,
735
- 0.03,
736
- max(0.02, plant.interest_rate - 2 * 0.03),
737
- plant.interest_rate + 2 * 0.03,
738
- num_samples,
803
+ plant.interest_rate, ir_std, ir_min, ir_max, num_samples,
739
804
  )
740
805
 
741
806
  variable_opex_price_samples = {}
@@ -768,12 +833,15 @@ def monte_carlo(plant,
768
833
  # ---- Apply sampled inputs ----
769
834
  plant_copy.operator_hourly_rate["rate"] = operator_hourlys[start:end]
770
835
 
771
- plant_copy.update_configuration(
772
- {
773
- "project_lifetime": project_lifetimes[start:end],
774
- "interest_rate": interests[start:end],
775
- }
776
- )
836
+ scalar_updates = {
837
+ "project_lifetime": project_lifetimes[start:end],
838
+ "interest_rate": interests[start:end],
839
+ }
840
+ if plant_utilizations is not None:
841
+ scalar_updates["plant_utilization"] = plant_utilizations[start:end]
842
+ if tax_rates is not None:
843
+ scalar_updates["tax_rate"] = tax_rates[start:end]
844
+ plant_copy.update_configuration(scalar_updates)
777
845
 
778
846
  for item in plant.variable_opex_inputs:
779
847
  plant_copy.variable_opex_inputs[item]["price"] = (
@@ -814,6 +882,10 @@ def monte_carlo(plant,
814
882
  "Operator hourly rate": operator_hourlys,
815
883
  "Project lifetime": project_lifetimes,
816
884
  "Interest rate": interests,
885
+ **({} if plant_utilizations is None
886
+ else {"Plant utilization": plant_utilizations}),
887
+ **({} if tax_rates is None
888
+ else {"Tax rate": tax_rates}),
817
889
  **{
818
890
  f"{k.replace('_', ' ').title()} price": v
819
891
  for k, v in variable_opex_price_samples.items()
@@ -288,6 +288,22 @@ class Equipment:
288
288
  Default is None (auto-resolved).
289
289
  target_year : int, optional
290
290
  Target year for inflation adjustment. Default is 2024.
291
+ erection_factor : float | None, optional
292
+ Erection factor override. Default is None (use process_type table).
293
+ piping_factor : float | None, optional
294
+ Piping factor override. Default is None (use process_type table).
295
+ instrumentation_factor : float | None, optional
296
+ Instrumentation & controls factor override. Default is None.
297
+ electrical_factor : float | None, optional
298
+ Electrical factor override. Default is None (use process_type table).
299
+ civil_factor : float | None, optional
300
+ Civil factor override. Default is None (use process_type table).
301
+ structural_factor : float | None, optional
302
+ Structural steel factor override. Default is None (use process_type table).
303
+ lagging_factor : float | None, optional
304
+ Lagging & painting factor override. Default is None (use process_type table).
305
+ material_factor : float | None, optional
306
+ Material factor override. Default is None (use material table).
291
307
 
292
308
  Methods
293
309
  -------
@@ -387,10 +403,16 @@ class Equipment:
387
403
  num_units: int | None = None,
388
404
  purchased_cost: float | None = None,
389
405
  cost_year: int | None = None,
390
- cost_func: (
391
- str | None
392
- ) = None, # explicit correlation key
406
+ cost_func: str | None = None,
393
407
  target_year: int = 2024,
408
+ erection_factor: float | None = None,
409
+ piping_factor: float | None = None,
410
+ instrumentation_factor: float | None = None,
411
+ electrical_factor: float | None = None,
412
+ civil_factor: float | None = None,
413
+ structural_factor: float | None = None,
414
+ lagging_factor: float | None = None,
415
+ material_factor: float | None = None,
394
416
  ):
395
417
 
396
418
  self.name = name
@@ -407,9 +429,48 @@ class Equipment:
407
429
  )
408
430
  self.target_year = target_year
409
431
  self._cost_func = cost_func
410
- self._db = (
411
- CostCorrelationDB()
412
- ) # always loads from the fixed CSV file
432
+ self._db = CostCorrelationDB()
433
+
434
+ valid_process_types = list(self.process_factors.keys())
435
+ if process_type not in self.process_factors:
436
+ raise ValueError(
437
+ f"Invalid process_type '{process_type}'. "
438
+ f"Valid options are: {valid_process_types}"
439
+ )
440
+ valid_materials = list(self.material_factors.keys())
441
+ if material not in self.material_factors:
442
+ raise ValueError(
443
+ f"Invalid material '{material}'. "
444
+ f"Valid options are: {valid_materials}"
445
+ )
446
+
447
+ _pf = self.process_factors[process_type]
448
+ self.erection_factor = (
449
+ erection_factor if erection_factor is not None else _pf["fer"]
450
+ )
451
+ self.piping_factor = (
452
+ piping_factor if piping_factor is not None else _pf["fp"]
453
+ )
454
+ self.instrumentation_factor = (
455
+ instrumentation_factor if instrumentation_factor is not None else _pf["fi"]
456
+ )
457
+ self.electrical_factor = (
458
+ electrical_factor if electrical_factor is not None else _pf["fel"]
459
+ )
460
+ self.civil_factor = (
461
+ civil_factor if civil_factor is not None else _pf["fc"]
462
+ )
463
+ self.structural_factor = (
464
+ structural_factor if structural_factor is not None else _pf["fs"]
465
+ )
466
+ self.lagging_factor = (
467
+ lagging_factor if lagging_factor is not None else _pf["fl"]
468
+ )
469
+ self.material_factor = (
470
+ material_factor
471
+ if material_factor is not None
472
+ else self.material_factors[material]
473
+ )
413
474
 
414
475
  if purchased_cost is not None:
415
476
  self.purchased_cost = purchased_cost
@@ -456,29 +517,15 @@ class Equipment:
456
517
  )
457
518
 
458
519
  def calculate_direct_cost(self) -> float:
459
-
460
- if self.process_type not in self.process_factors:
461
- raise ValueError(
462
- f"Process type not found: {self.process_type}"
463
- )
464
-
465
- if self.material not in self.material_factors:
466
- raise ValueError(
467
- f"Material not found: {self.material}"
468
- )
469
-
470
- factors = self.process_factors[self.process_type]
471
- fm = self.material_factors[self.material]
472
-
473
520
  self.direct_cost = self.purchased_cost * (
474
- (1 + factors["fp"]) * fm
521
+ (1 + self.piping_factor) * self.material_factor
475
522
  + (
476
- factors["fer"]
477
- + factors["fel"]
478
- + factors["fi"]
479
- + factors["fc"]
480
- + factors["fs"]
481
- + factors["fl"]
523
+ self.erection_factor
524
+ + self.electrical_factor
525
+ + self.instrumentation_factor
526
+ + self.civil_factor
527
+ + self.structural_factor
528
+ + self.lagging_factor
482
529
  )
483
530
  )
484
531
  return self.direct_cost