11//! Code for reading process availabilities CSV file
22use super :: super :: { format_items_with_cap, input_err_msg, read_csv, try_insert} ;
3- use crate :: process:: { ProcessActivityLimitsMap , ProcessID , ProcessMap } ;
3+ use crate :: process:: { Process , ProcessActivityLimitsMap , ProcessID , ProcessMap } ;
44use crate :: region:: parse_region_str;
55use crate :: time_slice:: TimeSliceInfo ;
66use crate :: units:: { Dimensionless , Year } ;
@@ -83,18 +83,25 @@ pub fn read_process_availabilities(
8383 model_dir : & Path ,
8484 processes : & ProcessMap ,
8585 time_slice_info : & TimeSliceInfo ,
86+ base_year : u32 ,
8687) -> Result < HashMap < ProcessID , ProcessActivityLimitsMap > > {
8788 let file_path = model_dir. join ( PROCESS_AVAILABILITIES_FILE_NAME ) ;
8889 let process_availabilities_csv = read_csv ( & file_path) ?;
89- read_process_availabilities_from_iter ( process_availabilities_csv, processes, time_slice_info)
90- . with_context ( || input_err_msg ( & file_path) )
90+ read_process_availabilities_from_iter (
91+ process_availabilities_csv,
92+ processes,
93+ time_slice_info,
94+ base_year,
95+ )
96+ . with_context ( || input_err_msg ( & file_path) )
9197}
9298
9399/// Process raw process availabilities input data into [`ProcessActivityLimitsMap`]s
94100fn read_process_availabilities_from_iter < I > (
95101 iter : I ,
96102 processes : & ProcessMap ,
97103 time_slice_info : & TimeSliceInfo ,
104+ base_year : u32 ,
98105) -> Result < HashMap < ProcessID , ProcessActivityLimitsMap > >
99106where
100107 I : Iterator < Item = ProcessAvailabilityRaw > ,
@@ -140,7 +147,7 @@ where
140147 }
141148 }
142149
143- validate_activity_limits_maps ( & map, processes, time_slice_info) ?;
150+ validate_activity_limits_maps ( & map, processes, time_slice_info, base_year ) ?;
144151
145152 Ok ( map)
146153}
@@ -150,38 +157,82 @@ fn validate_activity_limits_maps(
150157 all_availabilities : & HashMap < ProcessID , ProcessActivityLimitsMap > ,
151158 processes : & ProcessMap ,
152159 time_slice_info : & TimeSliceInfo ,
160+ base_year : u32 ,
153161) -> Result < ( ) > {
154162 for ( process_id, process) in processes {
155163 // A map of maps: the outer map is keyed by region and year; the inner one by time slice
156164 let map_for_process = all_availabilities
157165 . get ( process_id)
158166 . with_context ( || format ! ( "Missing availabilities for process {process_id}" ) ) ?;
159167
160- let mut missing_keys = Vec :: new ( ) ;
161- for ( region_id, year) in iproduct ! ( & process. regions, & process. years) {
162- if let Some ( map_for_region_year) = map_for_process. get ( & ( region_id. clone ( ) , * year) ) {
163- // There are at least some entries for this region/year combo; check if there are
164- // any time slices not covered
165- missing_keys. extend (
166- time_slice_info
167- . iter_ids ( )
168- . filter ( |ts| !map_for_region_year. contains_key ( ts) )
169- . map ( |ts| ( region_id, * year, ts) ) ,
170- ) ;
171- } else {
172- // No entries for this region/year combo: by definition no time slices are covered
173- missing_keys. extend ( time_slice_info. iter_ids ( ) . map ( |ts| ( region_id, * year, ts) ) ) ;
174- }
168+ check_missing_milestone_years ( process, map_for_process, base_year) ?;
169+ check_missing_time_slices ( process, map_for_process, time_slice_info) ?;
170+ }
171+
172+ Ok ( ( ) )
173+ }
174+
175+ /// Check every milestone year in which the process can be commissioned has availabilities.
176+ ///
177+ /// Entries for non-milestone years in which the process can be commissioned (which are only
178+ /// required for pre-defined assets, if at all) are not required and will be checked lazily when
179+ /// assets requiring them are constructed.
180+ fn check_missing_milestone_years (
181+ process : & Process ,
182+ map_for_process : & ProcessActivityLimitsMap ,
183+ base_year : u32 ,
184+ ) -> Result < ( ) > {
185+ let process_milestone_years = process
186+ . years
187+ . iter ( )
188+ . copied ( )
189+ . filter ( |& year| year >= base_year) ;
190+ let mut missing = Vec :: new ( ) ;
191+ for ( region_id, year) in iproduct ! ( & process. regions, process_milestone_years) {
192+ if !map_for_process. contains_key ( & ( region_id. clone ( ) , year) ) {
193+ missing. push ( ( region_id, year) ) ;
175194 }
195+ }
176196
177- ensure ! (
178- missing_keys. is_empty( ) ,
179- "Process {process_id} is missing availabilities for the following regions, years and \
180- time slices: {}",
181- format_items_with_cap( & missing_keys)
182- ) ;
197+ ensure ! (
198+ missing. is_empty( ) ,
199+ "Process {} is missing availabilities for the following regions and milestone years: {}" ,
200+ & process. id,
201+ format_items_with_cap( & missing)
202+ ) ;
203+
204+ Ok ( ( ) )
205+ }
206+
207+ /// Check that entries for all time slices are provided for any process/region/year combo for which
208+ /// we have any entries at all
209+ fn check_missing_time_slices (
210+ process : & Process ,
211+ map_for_process : & ProcessActivityLimitsMap ,
212+ time_slice_info : & TimeSliceInfo ,
213+ ) -> Result < ( ) > {
214+ let mut missing = Vec :: new ( ) ;
215+ for ( region_id, & year) in iproduct ! ( & process. regions, & process. years) {
216+ if let Some ( map_for_region_year) = map_for_process. get ( & ( region_id. clone ( ) , year) ) {
217+ // There are at least some entries for this region/year combo; check if there are
218+ // any time slices not covered
219+ missing. extend (
220+ time_slice_info
221+ . iter_ids ( )
222+ . filter ( |ts| !map_for_region_year. contains_key ( ts) )
223+ . map ( |ts| ( region_id, year, ts) ) ,
224+ ) ;
225+ }
183226 }
184227
228+ ensure ! (
229+ missing. is_empty( ) ,
230+ "Availabilities supplied for some, but not all time slices, for process {}. The following \
231+ regions, years and time slices are missing: {}",
232+ & process. id,
233+ format_items_with_cap( & missing)
234+ ) ;
235+
185236 Ok ( ( ) )
186237}
187238
0 commit comments