11import argparse
2- import io
32from pathlib import Path
3+ from typing import cast
44
55import polars as pl
66from cloudpathlib import S3Path
77
8- from utils .types import SB_scenario , electric_utility
8+ from utils .types import SBScenario , electric_utility
99
1010# Project root (rate-design-platform); independent of cwd or caller
1111_PROJECT_ROOT = Path (__file__ ).resolve ().parent .parent
1212RATE_DESIGN_DIR = _PROJECT_ROOT / "rate_design"
1313
14+ AWS_REGION = "us-west-2"
15+
16+ STORAGE_OPTIONS = {"aws_region" : AWS_REGION }
17+
1418
1519def define_electrical_tariff_key (
16- SB_scenario : SB_scenario ,
20+ SB_scenario : SBScenario ,
1721 electric_utility : electric_utility ,
18- has_hp : pl .Series ,
22+ has_hp : pl .Expr ,
1923) -> pl .Expr :
2024 if SB_scenario .analysis_type == "default" :
2125 return pl .lit (f"{ electric_utility } _{ SB_scenario } _default" )
22- elif (
23- SB_scenario .analysis_type == "seasonal"
24- or SB_scenario .analysis_type == "class_specific_seasonal"
25- ):
26+ elif SB_scenario .analysis_type in ["seasonal" , "class_specific_seasonal" ]:
2627 return (
2728 pl .when (has_hp )
2829 .then (pl .lit (f"{ electric_utility } _{ SB_scenario } _HP.csv" ))
@@ -33,124 +34,145 @@ def define_electrical_tariff_key(
3334
3435
3536def generate_electrical_tariff_mapping (
36- metadata_has_hp : pl .DataFrame ,
37- SB_scenario : SB_scenario ,
37+ lazy_metadata_has_hp : pl .LazyFrame ,
38+ SB_scenario : SBScenario ,
3839 electric_utility : electric_utility ,
39- ) -> pl .DataFrame :
40- has_hp = metadata_has_hp ["postprocess_group.has_hp" ]
41-
42- electrical_tariff_mapping_df = metadata_has_hp .select (
43- pl .col ("bldg_id" )
44- ).with_columns (
45- define_electrical_tariff_key (SB_scenario , electric_utility , has_hp ).alias (
46- "tariff_key"
47- )
40+ ) -> pl .LazyFrame :
41+ electrical_tariff_mapping_df = lazy_metadata_has_hp .select (
42+ pl .col ("bldg_id" ),
43+ define_electrical_tariff_key (
44+ SB_scenario , electric_utility , pl .col ("postprocess_group.has_hp" )
45+ ).alias ("tariff_key" ),
4846 )
4947
5048 return electrical_tariff_mapping_df
5149
5250
5351def map_electric_tariff (
54- SB_metadata_path : S3Path ,
52+ SB_metadata_lazy_df : pl . LazyFrame ,
5553 electric_utility : electric_utility ,
56- SB_scenario : SB_scenario ,
54+ SB_scenario : SBScenario ,
5755 state : str ,
58- ):
59- if not SB_metadata_path .exists ():
60- raise FileNotFoundError (f"SB metadata path { SB_metadata_path } does not exist" )
61-
62- metadata_df = pl .read_parquet (io .BytesIO (SB_metadata_path .read_bytes ()))
63- utility_metadata_df = metadata_df .filter (
56+ ) -> pl .LazyFrame :
57+ utility_metadata_df = SB_metadata_lazy_df .filter (
6458 pl .col ("sb.electric_utility" ) == electric_utility
6559 )
66- if utility_metadata_df .is_empty ():
67- return
6860
6961 metadata_has_hp = utility_metadata_df .select (
7062 pl .col ("bldg_id" , "postprocess_group.has_hp" )
7163 )
72- electrical_tariff_mapping_df = generate_electrical_tariff_mapping (
73- metadata_has_hp , SB_scenario , electric_utility
74- )
7564
76- print (electrical_tariff_mapping_df .head (20 ))
65+ # Check if there are any rows in the filtered dataframe
66+ test_sample = cast (pl .DataFrame , metadata_has_hp .head (1 ).collect ())
67+ if test_sample .is_empty ():
68+ raise ValueError (f"No rows found for electric utility { electric_utility } " )
7769
78- output_path = (
79- RATE_DESIGN_DIR
80- / state .lower ()
81- / "hp_rates"
82- / "data"
83- / "tariff_map"
84- / f"{ electric_utility } _{ SB_scenario } .csv"
70+ electrical_tariff_mapping_df = generate_electrical_tariff_mapping (
71+ metadata_has_hp , SB_scenario , electric_utility
8572 )
86- if not output_path .parent .exists ():
87- output_path .parent .mkdir (parents = True )
88- electrical_tariff_mapping_df .write_csv (output_path )
8973
90- return
74+ return electrical_tariff_mapping_df
9175
9276
93- def main () :
77+ if __name__ == "__main__" :
9478 parser = argparse .ArgumentParser (description = "Map electrical tariff." )
9579 parser .add_argument (
96- "--metadata_path" , required = True , help = "Base path for resstock data"
80+ "--metadata_path" ,
81+ required = True ,
82+ help = "Absolute or s3 path to ResStock metadata" ,
9783 )
9884 parser .add_argument ("--state" , required = True , help = "State code (e.g. RI)" )
85+ parser .add_argument ("--upgrade_id" , required = True , help = "Upgrade id (e.g. 00)" )
9986 parser .add_argument (
10087 "--electric_utility" , required = True , help = "Electric utility (e.g. Coned)"
10188 )
10289 parser .add_argument (
103- "--SB_scenario_name" , required = True , help = "SB scenario name (e.g. default_1)"
90+ "--SB_scenario_type" ,
91+ required = True ,
92+ help = "SB scenario type (e.g. default, seasonal, class_specific_seasonal)" ,
10493 )
10594 parser .add_argument (
10695 "--SB_scenario_year" , required = True , help = "SB scenario year (e.g. 2024)"
10796 )
97+ parser .add_argument (
98+ "--output_dir" ,
99+ default = None ,
100+ help = "Optional directory for output CSV; default is rate_design/<state>/hp_rates/data/tariff_map/" ,
101+ )
108102 args = parser .parse_args ()
109103
110104 #########################################################
111105 # For now, we will manually add the electric utility name column. Later on, the metadata parquet will be updated to include this column.
112106 # Assign first ~1/3 to Coned, next ~1/3 to National Grid, last ~1/3 to NYSEG.
113- metadata_path = S3Path (args .metadata_path )
114- metadata_df = pl .read_parquet (io .BytesIO (metadata_path .read_bytes ()))
115- n = len (metadata_df )
116- metadata_df = (
117- metadata_df .with_row_index ("_row_idx" )
118- .with_columns (
119- pl .when (pl .col ("_row_idx" ) < n // 3 )
120- .then (pl .lit ("Coned" ))
121- .when (pl .col ("_row_idx" ) < 2 * n // 3 )
122- .then (pl .lit ("National Grid" ))
123- .otherwise (pl .lit ("NYSEG" ))
124- .alias ("sb.electric_utility" )
107+ try : # If the metadata path is an S3 path, use the S3Path class.
108+ base_path = S3Path (args .metadata_path )
109+ metadata_path = (
110+ base_path
111+ / f"state={ args .state } "
112+ / f"upgrade={ args .upgrade_id } "
113+ / "metadata-sb.parquet"
114+ )
115+ if not metadata_path .exists ():
116+ raise FileNotFoundError (f"Metadata path { metadata_path } does not exist" )
117+ # Polars scan_parquet needs a string path; S3Path.as_uri() gives s3:// URL
118+ SB_metadata_lazy_df = pl .scan_parquet (
119+ str (metadata_path ), storage_options = STORAGE_OPTIONS
120+ )
121+ except ValueError :
122+ # If the metadata path is a local path, use the Path class.
123+ base_path = Path (args .metadata_path )
124+ metadata_path = (
125+ base_path
126+ / f"state={ args .state } "
127+ / f"upgrade={ args .upgrade_id } "
128+ / "metadata-sb.parquet"
125129 )
126- .drop ("_row_idx" )
130+ if not metadata_path .exists ():
131+ raise FileNotFoundError (f"Metadata path { metadata_path } does not exist" )
132+ SB_metadata_lazy_df = pl .scan_parquet (str (metadata_path ))
133+
134+ # Add dummy electric utility column (deterministic by bldg_id). Later this column will be pre-existing in the SB metadata parquet.
135+ SB_metadata_lazy_df_with_electric_utility = SB_metadata_lazy_df .with_columns (
136+ pl .when (pl .col ("bldg_id" ).hash () % 3 == 0 )
137+ .then (pl .lit ("Coned" ))
138+ .when (pl .col ("bldg_id" ).hash () % 3 == 1 )
139+ .then (pl .lit ("National Grid" ))
140+ .otherwise (pl .lit ("NYSEG" ))
141+ .alias ("sb.electric_utility" )
127142 )
128- temp_path = metadata_path .parent / f"{ metadata_path .stem } _temp.parquet"
129- buf = io .BytesIO ()
130- metadata_df .write_parquet (buf )
131- temp_path .write_bytes (buf .getvalue ())
132143 #########################################################
133144
134- if (
135- args .SB_scenario_name != "default"
136- and args .SB_scenario_name != "seasonal"
137- and args .SB_scenario_name != "class_specific_seasonal"
138- ):
139- raise ValueError (f"Invalid SB scenario name: { args .SB_scenario_name } " )
140-
141- sb_scenario = SB_scenario (args .SB_scenario_name , args .SB_scenario_year )
142- map_electric_tariff (
143- SB_metadata_path = temp_path ,
145+ sb_scenario = SBScenario (args .SB_scenario_type , args .SB_scenario_year )
146+ electrical_tariff_mapping_df = map_electric_tariff (
147+ SB_metadata_lazy_df = SB_metadata_lazy_df_with_electric_utility ,
144148 electric_utility = args .electric_utility ,
145149 SB_scenario = sb_scenario ,
146150 state = args .state ,
147151 )
148-
149- #########################################################
150- # Remove the temp file that contains electric utility name column.
151- temp_path .unlink ()
152- #########################################################
153-
154-
155- if __name__ == "__main__" :
156- main ()
152+ if args .output_dir :
153+ try :
154+ base_path = S3Path (args .output_dir )
155+ output_path = base_path / f"{ args .electric_utility } _{ sb_scenario } .csv"
156+ if not output_path .parent .exists ():
157+ output_path .parent .mkdir (parents = True )
158+ electrical_tariff_mapping_df .sink_csv (
159+ str (output_path ), storage_options = STORAGE_OPTIONS
160+ )
161+ except ValueError :
162+ base_path = Path (args .output_dir )
163+ output_path = base_path / f"{ args .electric_utility } _{ sb_scenario } .csv"
164+ if not output_path .parent .exists ():
165+ output_path .parent .mkdir (parents = True )
166+ electrical_tariff_mapping_df .sink_csv (str (output_path ))
167+ else :
168+ output_path = (
169+ RATE_DESIGN_DIR
170+ / args .state .lower ()
171+ / "hp_rates"
172+ / "data"
173+ / "tariff_map"
174+ / f"{ args .electric_utility } _{ sb_scenario } .csv"
175+ )
176+ if not output_path .parent .exists ():
177+ output_path .parent .mkdir (parents = True )
178+ electrical_tariff_mapping_df .sink_csv (str (output_path ))
0 commit comments