2222import PySAM .Utilityrate5 as UtilityRate
2323
2424import geophires_x .Model as Model
25+ from geophires_x .OptionList import EndUseOptions
2526
2627
2728@lru_cache (maxsize = 12 )
@@ -57,6 +58,13 @@ def calculate_sam_economics(
5758 ('IRR' , single_owner .Outputs .project_return_aftertax_irr , '%' ),
5859 ('NPV' , single_owner .Outputs .project_return_aftertax_npv * 1e-6 , 'MUSD' ),
5960 ('CAPEX' , single_owner .Outputs .adjusted_installed_cost * 1e-6 , 'MUSD' ),
61+
62+ ('Electricity Price (cents/kWh)' , [p * 1e-2 for p in single_owner .Outputs .cf_ppa_price ], 'cents/kWh' ),
63+ ('Electricity Ann. Rev. (MUSD/yr)' , [e * 1e-6 for e in single_owner .Outputs .cf_energy_value ], '(MUSD/yr)' ),
64+
65+ # TODO determine if this is the 'appropriate' cashflow variable
66+ ('Project Net Rev (MUSD/yr)' , [c * 1e-6 for c in single_owner .Outputs .cf_pretax_cashflow ], 'MUSD/yr' ),
67+
6068 # ('Gross Output', gt.Outputs.gross_output, 'MW'),
6169 # ('Net Output', gt.Outputs.gross_output - gt.Outputs.pump_work, 'MW')
6270 ]
@@ -110,7 +118,7 @@ def _get_single_owner_parameters(model: Model) -> dict[str, Any]:
110118
111119
112120@lru_cache (maxsize = 12 )
113- def _get_revenue_and_cashflow_profile (model : Model ):
121+ def calculate_sam_economics_cashflow (model : Model ):
114122 """
115123 ENERGY
116124 Electricity Provided -> cf_energy_sales
@@ -126,12 +134,95 @@ def _get_revenue_and_cashflow_profile(model: Model):
126134 Net Revenue -> cf_total_revenue
127135 """
128136
137+ econ = model .economics
138+ sam_econ = calculate_sam_economics (model )
139+
140+ construction_years = model .surfaceplant .construction_years .value
141+ plant_lifetime = model .surfaceplant .plant_lifetime .value
142+ total_duration = plant_lifetime + construction_years
143+
144+ # econ.ElecRevenue.value = [0.0] * total_duration
145+ # econ.ElecCummRevenue.value = [0.0] * total_duration
146+ # econ.HeatRevenue.value = [0.0] * total_duration
147+ # econ.HeatCummRevenue.value = [0.0] * total_duration
148+ # econ.CoolingRevenue.value = [0.0] * total_duration
149+ # econ.CoolingCummRevenue.value = [0.0] * total_duration
150+ # econ.CarbonRevenue.value = [0.0] * total_duration
151+ # econ.CarbonCummCashFlow.value = [0.0] * total_duration
152+ # econ.TotalRevenue.value = [0.0] * total_duration
153+ # econ.TotalCummRevenue.value = [0.0] * total_duration
154+ # econ.CarbonThatWouldHaveBeenProducedTotal.value = 0.0
155+
156+ def _cumm (cash_flow : list ) -> list :
157+ cumm_cash_flow = [0.0 ] * total_duration
158+ for i in range (construction_years , total_duration , 1 ):
159+ cumm_cash_flow [i ] = cumm_cash_flow [i - 1 ] + cash_flow [i ]
160+
161+ return cumm_cash_flow
162+
163+ # Based on the style of the project, calculate the revenue & cumulative revenue
164+ if model .surfaceplant .enduse_option .value == EndUseOptions .ELECTRICITY :
165+ econ .ElecPrice .value = sam_econ ['Electricity Price (cents/kWh)' ]['value' ].copy ()
166+ econ .ElecRevenue .value = sam_econ ['Electricity Ann. Rev. (MUSD/yr)' ]['value' ].copy () # FIXME WIP
167+ econ .ElecCummRevenue .value = _cumm (econ .ElecRevenue .value )
168+
169+ # econ.ElecRevenue.value, econ.ElecCummRevenue.value = CalculateRevenue(
170+ # model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value,
171+ # model.surfaceplant.NetkWhProduced.value, econ.ElecPrice.value)
172+ # econ.TotalRevenue.value = econ.ElecRevenue.value.copy()
173+ # econ.TotalCummRevenue.value = econ.ElecCummRevenue.value
174+ else :
175+ raise ValueError (f'Unexpected End-Use Option: { model .surfaceplant .enduse_option .value } ' )
176+
177+ if econ .DoCarbonCalculations .value :
178+ raise NotImplementedError
179+
180+ # FIXME TODO
181+ # econ.CarbonRevenue.value, econ.CarbonCummCashFlow.value, econ.CarbonThatWouldHaveBeenProducedAnnually.value, \
182+ # econ.CarbonThatWouldHaveBeenProducedTotal.value = econ.CalculateCarbonRevenue(model,
183+ # model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value,
184+ # econ.CarbonPrice.value, econ.GridCO2Intensity.value, econ.NaturalGasCO2Intensity.value,
185+ # model.surfaceplant.NetkWhProduced.value, model.surfaceplant.HeatkWhProduced.value)
186+ # for i in range(model.surfaceplant.construction_years.value, model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value, 1):
187+ # econ.TotalRevenue.value[i] = econ.TotalRevenue.value[i] + econ.CarbonRevenue.value[i]
188+ # #econ.TotalCummRevenue.value[i] = econ.TotalCummRevenue.value[i] + econ.CarbonCummCashFlow.value[i]
189+
190+ # FIXME WIP TODO pass/reconcile non-1 construction years in/from SAM
191+ # for the sake of display, insert zeros at the beginning of the pricing arrays
192+ for i in range (0 , model .surfaceplant .construction_years .value , 1 ):
193+ # econ.ElecPrice.value.insert(0, 0.0)
194+ econ .HeatPrice .value .insert (0 , 0.0 )
195+ econ .CoolingPrice .value .insert (0 , 0.0 )
196+ econ .CarbonPrice .value .insert (0 , 0.0 )
197+
198+ # Insert the cost of construction into the front of the array that will be used to calculate NPV
199+ # the convention is that the upfront CAPEX is negative
200+ # This is the same for all projects
201+ # ProjectCAPEXPerConstructionYear = econ.CCap.value / model.surfaceplant.construction_years.value
202+ # for i in range(0, model.surfaceplant.construction_years.value, 1):
203+ # econ.TotalRevenue.value[i] = -1.0 * ProjectCAPEXPerConstructionYear
204+ # econ.TotalCummRevenue.value[i] = -1.0 * ProjectCAPEXPerConstructionYear
205+ # econ.TotalRevenue.value, econ.TotalCummRevenue.value = CalculateTotalRevenue(
206+ # model.surfaceplant.plant_lifetime.value, model.surfaceplant.construction_years.value, econ.CCap.value,
207+ # econ.Coam.value, econ.TotalRevenue.value, econ.TotalCummRevenue.value)
208+
209+ econ .TotalRevenue .value = sam_econ ['Project Net Rev (MUSD/yr)' ]['value' ].copy ()
210+
211+ econ .TotalCummRevenue .value = _cumm (econ .TotalRevenue .value )
212+
213+
129214def _get_average_net_generation_MW (model : Model ) -> float :
130215 return np .average (model .surfaceplant .NetElectricityProduced .value )
131216
132217
133- def _sig_figs (val : float , num_sig_figs : int ) -> float :
218+ def _sig_figs (val : float | list [ float ] | tuple [ float ] , num_sig_figs : int ) -> float :
134219 if val is None :
135220 return None
136221
137- return float ('%s' % float (f'%.{ num_sig_figs } g' % val )) # pylint: disable=consider-using-f-string
222+ if isinstance (val , list ) or isinstance (val , tuple ):
223+ return [_sig_figs (v , num_sig_figs ) for v in val ]
224+
225+ try :
226+ return float ('%s' % float (f'%.{ num_sig_figs } g' % val )) # pylint: disable=consider-using-f-string
227+ except TypeError as te :
228+ raise RuntimeError from te
0 commit comments