@@ -638,6 +638,140 @@ def handle_pjm(args):
638638 client .cleanup ()
639639
640640
641+ def handle_isone (args ):
642+ """Handle ISO-NE-specific data download logic (updated for new ISONEClient)."""
643+ from lib .iso .isone import ISONEClient
644+
645+ logger .info (f"Processing ISO-NE data request: { args .data_type } " )
646+
647+ client = ISONEClient ()
648+ start = args .start
649+ end_excl = start + timedelta (days = args .duration )
650+ success = False
651+
652+ def _yyyymmdd (d : date ) -> str :
653+ return d .strftime ("%Y%m%d" )
654+
655+ def _download_fivemin_lmp (location_id = None ):
656+ """Download 5-minute RT LMPs via REST (/fiveminutelmp). Saves one JSON per day."""
657+ out_paths = []
658+ for d in (start + timedelta (days = i ) for i in range (args .duration )):
659+ day_str = _yyyymmdd (d )
660+ loc = int (location_id ) if location_id is not None else 4000 # ISO-NE Internal Hub
661+ path = f"fiveminutelmp/day/{ day_str } /location/{ loc } "
662+ payload = client ._request_json (path , authenticated = True )
663+ suffix = f"_loc{ int (location_id )} " if location_id is not None else ""
664+ out_path = client .config .data_dir / f"fiveminutelmp_{ day_str } { suffix } .json"
665+ client ._save_json (payload , out_path )
666+ out_paths .append (out_path )
667+ return out_paths
668+
669+ def _download_hourly_rcp_final ():
670+ """Download hourly final regulation clearing prices via REST (/hourlyrcp/final)."""
671+ out_paths = []
672+ for d in (start + timedelta (days = i ) for i in range (args .duration )):
673+ day_str = _yyyymmdd (d )
674+ path = f"hourlyrcp/final/day/{ day_str } "
675+ payload = client ._request_json (path , authenticated = True )
676+ out_path = client .config .data_dir / f"hourlyrcp_final_{ day_str } .json"
677+ client ._save_json (payload , out_path )
678+ out_paths .append (out_path )
679+ return out_paths
680+
681+ try :
682+ if args .data_type == "lmp" :
683+ # LMP types: da_hourly, rt_5min
684+ lmp_type = getattr (args , "lmp_type" , "da_hourly" )
685+
686+ if lmp_type == "da_hourly" :
687+ logger .info ("Downloading ISO-NE Day-Ahead Hourly LMP (REST)..." )
688+ paths = client .get_hourly_lmp (start , end_excl , market = "da" , report = "final" )
689+ success = bool (paths )
690+
691+ elif lmp_type == "rt_5min" :
692+ logger .info ("Downloading ISO-NE Real-Time 5-Minute LMP (REST)..." )
693+ location_id = getattr (args , "location_id" , None )
694+ paths = _download_fivemin_lmp (location_id = location_id )
695+ success = bool (paths )
696+
697+ else :
698+ logger .error (f"Invalid LMP type: { lmp_type } " )
699+ return False
700+
701+ elif args .data_type == "ancillary" :
702+ # Ancillary types: 5min_reg, hourly_reg, 5min_reserves, hourly_reserves
703+ anc_type = getattr (args , "anc_type" , "5min_reg" )
704+
705+ if anc_type == "5min_reg" :
706+ logger .info ("Downloading ISO-NE 5-Minute Regulation Clearing Prices (Final)..." )
707+ paths = client .get_5min_regulation_prices (start , end_excl )
708+ success = bool (paths )
709+
710+ elif anc_type == "hourly_reg" :
711+ logger .info ("Downloading ISO-NE Hourly Regulation Clearing Prices (Final)..." )
712+ paths = _download_hourly_rcp_final ()
713+ success = bool (paths )
714+
715+ elif anc_type == "5min_reserves" :
716+ # Closest supported feed in the new client: Real-Time Hourly Operating Reserve
717+ location_id = getattr (args , "location_id" , 7000 )
718+ logger .info (
719+ f"Downloading ISO-NE Real-Time Hourly Operating Reserve (location { location_id } )..."
720+ )
721+ paths = client .get_real_time_hourly_operating_reserve (
722+ start , end_excl , location_id = location_id
723+ )
724+ success = bool (paths )
725+
726+ elif anc_type == "hourly_reserves" :
727+ location_id = getattr (args , "location_id" , 7000 )
728+ logger .info (
729+ f"Downloading ISO-NE Day-Ahead Hourly Operating Reserve (location { location_id } )..."
730+ )
731+ paths = client .get_day_ahead_hourly_operating_reserve (
732+ start , end_excl , location_id = location_id
733+ )
734+ success = bool (paths )
735+
736+ else :
737+ logger .error (f"Invalid ancillary type: { anc_type } " )
738+ return False
739+
740+ elif args .data_type == "demand" :
741+ # Demand types: 5min, hourly_da
742+ demand_type = getattr (args , "demand_type" , "5min" )
743+
744+ if demand_type == "5min" :
745+ logger .info ("Downloading ISO-NE 5-Minute System Demand (REST)..." )
746+ paths = client .get_5min_system_demand (start , end_excl )
747+ success = bool (paths )
748+
749+ elif demand_type == "hourly_da" :
750+ logger .info ("Downloading ISO-NE Day-Ahead Hourly Demand (REST)..." )
751+ paths = client .get_day_ahead_hourly_demand (start , end_excl )
752+ success = bool (paths )
753+
754+ else :
755+ logger .error (f"Invalid demand type: { demand_type } " )
756+ return False
757+
758+ else :
759+ logger .error (f"Unknown ISO-NE data type: { args .data_type } " )
760+ logger .info ("Available types: lmp, ancillary, demand" )
761+ return False
762+
763+ if success :
764+ logger .info ("✅ ISO-NE data downloaded successfully." )
765+ else :
766+ logger .error ("❌ Failed to download ISO-NE data." )
767+
768+ return success
769+
770+ except Exception as e :
771+ logger .error (f"Error processing ISO-NE data: { e } " , exc_info = True )
772+ return False
773+
774+
641775def handle_weather (args ):
642776 """Handle weather data download logic."""
643777 from lib .weather .client import WeatherClient
@@ -712,7 +846,7 @@ def main():
712846
713847 parser .add_argument (
714848 "--iso" ,
715- choices = ["caiso" , "miso" , "nyiso" , "bpa" , "spp" , "pjm" ],
849+ choices = ["caiso" , "miso" , "nyiso" , "bpa" , "spp" , "pjm" , "isone" ],
716850 help = "Independent System Operator" ,
717851 )
718852
@@ -761,7 +895,7 @@ def main():
761895 "rt_5min" ,
762896 "rt_hourly" ,
763897 ],
764- help = "For MISO and PJM LMP: type of LMP data to download" ,
898+ help = "For MISO, PJM, and ISO-NE LMP: type of LMP data to download" ,
765899 )
766900
767901 parser .add_argument (
@@ -864,6 +998,24 @@ def main():
864998 help = "For PJM Ancillary Services: type of AS data" ,
865999 )
8661000
1001+ parser .add_argument (
1002+ "--anc-type" ,
1003+ choices = ["5min_reg" , "hourly_reg" , "5min_reserves" , "hourly_reserves" ],
1004+ help = "For ISO-NE Ancillary Services: type of AS data" ,
1005+ )
1006+
1007+ parser .add_argument (
1008+ "--demand-type" ,
1009+ choices = ["5min" , "hourly_da" ],
1010+ help = "For ISO-NE Demand data: type of demand data" ,
1011+ )
1012+
1013+ parser .add_argument (
1014+ "--location-id" ,
1015+ type = int ,
1016+ help = "For ISO-NE location ID: Needed for full day 5min data" ,
1017+ )
1018+
8671019 parser .add_argument (
8681020 "--include-solar" ,
8691021 action = "store_true" ,
@@ -927,6 +1079,7 @@ def main():
9271079 "bpa" : handle_bpa ,
9281080 "spp" : handle_spp ,
9291081 "pjm" : handle_pjm ,
1082+ "isone" : handle_isone ,
9301083 }
9311084
9321085 handler = handlers .get (args .iso )
0 commit comments