@@ -35,10 +35,27 @@ def to_df(self) -> pl.DataFrame:
3535 })
3636
3737
38- # functions for generating dataframe rows for capex projects
3938def get_synthetic_initial_capex_projects (
4039 start_year : int , initial_ratebase : float , depreciation_lifetime : int
4140) -> pl .DataFrame :
41+ """
42+ Generate synthetic capex projects to represent the projects that make up the initial ratebase.
43+
44+ Creates a series of historical capex projects that would result in the given initial ratebase value, assuming straight-line depreciation. Uses the triangular number formula to create a uniform distribution of projects over the depreciation lifetime, where each project has the same original cost. Projects are distributed evenly over depreciation_lifetime years leading up to start_year.
45+
46+ Args:
47+ start_year: The first year of the model
48+ initial_ratebase: The target ratebase value at start_year
49+ depreciation_lifetime: The blended depreciation lifetime for the synthetic projects
50+
51+ Returns:
52+ pl.DataFrame with columns:
53+ - project_year: Year the project was initiated
54+ - project_type: "synthetic_initial"
55+ - original_cost: Cost of the project
56+ - depreciation_lifetime: Depreciation lifetime in years
57+ - retirement_year: Year the project is fully depreciated
58+ """
4259 total_weight = (depreciation_lifetime * (depreciation_lifetime + 1 ) / 2 ) / depreciation_lifetime
4360 est_original_cost_per_year = initial_ratebase / total_weight
4461 project_years = range (start_year - depreciation_lifetime + 1 , start_year + 1 )
@@ -56,11 +73,34 @@ def get_non_lpp_gas_capex_projects(
5673 current_ratebase : float ,
5774 baseline_non_lpp_gas_ratebase_growth : float ,
5875 depreciation_lifetime : int ,
76+ construction_inflation_rate : float ,
5977) -> pl .DataFrame :
78+ """
79+ Generate capex projects for non-LPP (non-leak prone pipe) gas infrastructure.
80+
81+ These represent routine gas infrastructure investments not related to pipe replacement or npas,
82+ such as meter replacements, regulator stations, etc. The cost is calculated as a
83+ percentage of current ratebase, adjusted for construction cost inflation.
84+
85+ Args:
86+ year: The year to generate projects for
87+ current_ratebase: Current value of the gas utility's ratebase
88+ baseline_non_lpp_gas_ratebase_growth: Annual growth rate for non-LPP capex as fraction of ratebase
89+ depreciation_lifetime: Blended depreciation lifetime in years for these projects
90+ construction_inflation_rate: Annual inflation rate for construction costs
91+
92+ Returns:
93+ pl.DataFrame with columns:
94+ - project_year: Year the project was initiated
95+ - project_type: "misc" for miscellaneous gas infrastructure
96+ - original_cost: Cost of the project
97+ - depreciation_lifetime: Depreciation lifetime in years
98+ - retirement_year: Year the project is fully depreciated
99+ """
60100 return CapexProject (
61101 project_year = year ,
62102 project_type = "misc" ,
63- original_cost = current_ratebase * baseline_non_lpp_gas_ratebase_growth ,
103+ original_cost = current_ratebase * baseline_non_lpp_gas_ratebase_growth * ( 1 + construction_inflation_rate ) ,
64104 depreciation_lifetime = depreciation_lifetime ,
65105 ).to_df ()
66106
@@ -72,18 +112,29 @@ def get_lpp_gas_capex_projects(
72112 depreciation_lifetime : int ,
73113) -> pl .DataFrame :
74114 """
75- Inputs:
76- - year: int
77- - gas_bau_lpp_costs_per_year: pl.DataFrame
78- - columns: year, cost
79- - year is not required to be unique
80- - npa_projects: pl.DataFrame
81- - npa columns
82- - depreciation_lifetime: int
83-
84- Outputs:
85- - pl.DataFrame
86- - capex project columns
115+ Generate capex projects for leak-prone pipe (LPP) replacement in the gas system.
116+
117+ This function calculates the remaining pipe replacement costs after accounting for pipe
118+ replacements avoided by NPA projects. If NPAs avoid all planned pipe replacements in a given
119+ year, returns an empty dataframe.
120+
121+ Args:
122+ year: The year to generate projects for
123+ gas_bau_lpp_costs_per_year: DataFrame containing business-as-usual pipe replacement costs
124+ with columns:
125+ - year: Year of planned replacement
126+ - cost: Cost of planned replacement
127+ Note: Multiple entries may exist per year
128+ npa_projects: DataFrame containing NPA project details, used to calculate avoided pipe costs
129+ depreciation_lifetime: Depreciation lifetime in years for pipe replacement projects
130+
131+ Returns:
132+ pl.DataFrame with columns:
133+ - project_year: Year the project was initiated
134+ - project_type: "pipeline" for pipe replacement projects
135+ - original_cost: Cost of the project after subtracting avoided costs
136+ - depreciation_lifetime: Depreciation lifetime in years
137+ - retirement_year: Year the project is fully depreciated
87138 """
88139 npas_this_year = npa_projects .filter (pl .col ("project_year" ) == year )
89140 npa_pipe_costs_avoided = compute_npa_pipe_cost_avoided_from_df (year , npas_this_year )
@@ -107,11 +158,33 @@ def get_non_npa_electric_capex_projects(
107158 current_ratebase : float ,
108159 baseline_electric_ratebase_growth : float ,
109160 depreciation_lifetime : int ,
161+ construction_inflation_rate : float ,
110162) -> pl .DataFrame :
163+ """
164+ Generate capex projects for non-NPA non-grid upgrade electric system upgrades.
165+
166+ This function calculates the baseline capital expenditures for the electric system,
167+ excluding NPA-related projects. The expenditure grows with both the baseline growth rate
168+ and construction inflation.
169+
170+ Args:
171+ year: The year to generate projects for
172+ current_ratebase: Current value of the electric utility's ratebase
173+ baseline_electric_ratebase_growth: Annual growth rate of non-NPA electric capex as fraction of ratebase
174+ depreciation_lifetime: Blended depreciation lifetime in years for electric system projects
175+ construction_inflation_rate: Annual inflation rate for construction costs
176+
177+ Returns:
178+ pl.DataFrame with columns:
179+ - project_year: Year the project was initiated
180+ - project_type: "misc" for miscellaneous electric system upgrades
181+ - original_cost: Cost of the project including construction inflation
182+ - depreciation_lifetime: Depreciation lifetime in years
183+ """
111184 return CapexProject (
112185 project_year = year ,
113186 project_type = "misc" ,
114- original_cost = current_ratebase * baseline_electric_ratebase_growth ,
187+ original_cost = current_ratebase * baseline_electric_ratebase_growth * ( 1 + construction_inflation_rate ) ,
115188 depreciation_lifetime = depreciation_lifetime ,
116189 ).to_df ()
117190
@@ -124,6 +197,28 @@ def get_grid_upgrade_capex_projects(
124197 distribution_cost_per_peak_kw_increase : float ,
125198 grid_upgrade_depreciation_lifetime : int ,
126199) -> pl .DataFrame :
200+ """
201+ Generate capex projects for grid upgrades needed to support NPA installations.
202+
203+ This function calculates the required grid upgrades based on the peak power increase
204+ from heat pumps and air conditioners installed as part of NPA projects. The cost
205+ scales linearly with the total peak power increase.
206+
207+ Args:
208+ year: The year to generate projects for
209+ npa_projects: DataFrame containing NPA project details
210+ peak_hp_kw: Peak power draw in kW for a heat pump
211+ peak_aircon_kw: Peak power draw in kW for an air conditioner
212+ distribution_cost_per_peak_kw_increase: Cost per kW of increasing grid capacity in year of project
213+ grid_upgrade_depreciation_lifetime: Depreciation lifetime in years for grid upgrades
214+
215+ Returns:
216+ pl.DataFrame with columns:
217+ - project_year: Year the project was initiated
218+ - project_type: "grid_upgrade" for grid capacity upgrades
219+ - original_cost: Total cost of grid upgrades
220+ - depreciation_lifetime: Depreciation lifetime in years
221+ """
127222 npas_this_year = npa_projects .filter (pl .col ("project_year" ) == year )
128223 peak_kw_increase = compute_peak_kw_increase_from_df (year , npas_this_year , peak_hp_kw , peak_aircon_kw )
129224 if peak_kw_increase > 0 :
@@ -140,6 +235,25 @@ def get_grid_upgrade_capex_projects(
140235def get_npa_capex_projects (
141236 year : int , npa_projects : pl .DataFrame , npa_install_cost : float , npa_lifetime : int
142237) -> pl .DataFrame :
238+ """
239+ Generate capex projects for NPA (non-pipe alternative) installations.
240+
241+ This function calculates the capital costs associated with installing NPAs in a given year.
242+ The total cost is based on the number of heat pump conversions and the per-unit installation cost.
243+
244+ Args:
245+ year: The year to generate projects for
246+ npa_projects: DataFrame containing NPA project details
247+ npa_install_cost: Cost per household of installing an NPA
248+ npa_lifetime: Expected lifetime in years of an NPA installation
249+
250+ Returns:
251+ pl.DataFrame with columns:
252+ - project_year: Year the project was initiated
253+ - project_type: "npa" for NPA installations
254+ - original_cost: Total cost of NPA installations
255+ - depreciation_lifetime: Depreciation lifetime in years
256+ """
143257 npas_this_year = npa_projects .filter (pl .col ("project_year" ) == year )
144258 npa_total_cost = npa_install_cost * compute_hp_converts_from_df (
145259 year , npas_this_year , cumulative = False , npa_only = True
@@ -152,8 +266,24 @@ def get_npa_capex_projects(
152266 return return_empty_capex_df ()
153267
154268
155- # functions for computing things given a dataframe of capex projects
156269def compute_ratebase_from_capex_projects (year : int , df : pl .DataFrame ) -> float :
270+ """Compute the ratebase value for a given year from capital projects.
271+
272+ For each project, the ratebase value declines linearly from the original cost to zero over the depreciation lifetime.
273+ Projects that haven't started yet (year < project_year) have zero ratebase value.
274+ Projects that are fully depreciated have zero ratebase value.
275+ Projects that are in the year of the project have the full original cost added to the ratebase.
276+
277+ Args:
278+ year: The year to compute ratebase for
279+ df: DataFrame containing capital projects with columns:
280+ - project_year: int - Year project was initiated
281+ - original_cost: float - Original cost of the project
282+ - depreciation_lifetime: int - Number of years to depreciate over
283+
284+ Returns:
285+ float: Total ratebase value for the year across all projects
286+ """
157287 df = df .with_columns (
158288 pl .when (pl .lit (year ) < pl .col ("project_year" ))
159289 .then (pl .lit (0 ))
@@ -164,6 +294,21 @@ def compute_ratebase_from_capex_projects(year: int, df: pl.DataFrame) -> float:
164294
165295
166296def compute_depreciation_expense_from_capex_projects (year : int , df : pl .DataFrame ) -> float :
297+ """Compute annual depreciation expense for capital projects.
298+
299+ For each project, depreciation expense is the original cost divided evenly over the depreciation lifetime.
300+ Depreciation starts the year after the project year and continues for the depreciation lifetime.
301+
302+ Args:
303+ year: The year to compute depreciation expense for
304+ df: DataFrame containing capital projects with columns:
305+ - project_year: int - Year project was initiated
306+ - original_cost: float - Original cost of the project
307+ - depreciation_lifetime: int - Number of years to depreciate over
308+
309+ Returns:
310+ float: Total depreciation expense for the year across all projects
311+ """
167312 return float (
168313 df .select (
169314 pl .when (
0 commit comments