1use super::{input_err_msg, read_csv_optional};
3use crate::agent::AgentID;
4use crate::asset::Asset;
5use crate::id::IDCollection;
6use crate::process::ProcessMap;
7use crate::region::RegionID;
8use crate::units::Capacity;
9use anyhow::{Context, Result, ensure};
10use indexmap::IndexSet;
11use itertools::Itertools;
12use serde::Deserialize;
13use std::path::Path;
14use std::rc::Rc;
15
16const ASSETS_FILE_NAME: &str = "assets.csv";
17
18#[derive(Default, Deserialize, PartialEq)]
19struct AssetRaw {
20 process_id: String,
21 region_id: String,
22 agent_id: String,
23 capacity: Capacity,
24 commission_year: u32,
25 #[serde(default)]
26 max_decommission_year: Option<u32>,
27}
28
29pub fn read_assets(
42 model_dir: &Path,
43 agent_ids: &IndexSet<AgentID>,
44 processes: &ProcessMap,
45 region_ids: &IndexSet<RegionID>,
46) -> Result<Vec<Asset>> {
47 let file_path = model_dir.join(ASSETS_FILE_NAME);
48 let assets_csv = read_csv_optional(&file_path)?;
49 read_assets_from_iter(assets_csv, agent_ids, processes, region_ids)
50 .with_context(|| input_err_msg(&file_path))
51}
52
53fn read_assets_from_iter<I>(
66 iter: I,
67 agent_ids: &IndexSet<AgentID>,
68 processes: &ProcessMap,
69 region_ids: &IndexSet<RegionID>,
70) -> Result<Vec<Asset>>
71where
72 I: Iterator<Item = AssetRaw>,
73{
74 iter.map(|asset| -> Result<_> {
75 let agent_id = agent_ids.get_id(&asset.agent_id)?;
76 let process = processes
77 .get(asset.process_id.as_str())
78 .with_context(|| format!("Invalid process ID: {}", &asset.process_id))?;
79 let region_id = region_ids.get_id(&asset.region_id)?;
80
81 ensure!(
83 process.years.contains(&asset.commission_year),
84 "Agent {} has asset with commission year {}, not within process {} commission years: {:?}",
85 asset.agent_id,
86 asset.commission_year,
87 asset.process_id,
88 process.years
89 );
90 ensure!(
92 process.parameters.contains_key(&(region_id.clone(), asset.commission_year)),
93 "Parameters for process {} do not contain entry for year {}, required for asset in agent {}",
94 asset.process_id,
95 asset.commission_year,
96 asset.agent_id,
97 );
98 ensure!(
99 process.flows.contains_key(&(region_id.clone(), asset.commission_year)),
100 "Flows for process {} do not contain entry for year {}, required for asset in agent {}",
101 asset.process_id,
102 asset.commission_year,
103 asset.agent_id,
104 );
105
106 Asset::new_future_with_max_decommission(
107 agent_id.clone(),
108 Rc::clone(process),
109 region_id.clone(),
110 asset.capacity,
111 asset.commission_year,
112 asset.max_decommission_year,
113 )
114 })
115 .try_collect()
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::fixture::{processes, region_ids};
122
123 use itertools::assert_equal;
124 use rstest::{fixture, rstest};
125 use std::iter;
126
127 #[fixture]
128 fn agent_ids() -> IndexSet<AgentID> {
129 IndexSet::from(["agent1".into()])
130 }
131
132 #[rstest]
133 #[case::max_decommission_year_provided(Some(2015))]
134 #[case::max_decommission_year_not_provided(None)]
135 fn test_read_assets_from_iter_valid(
136 #[case] max_decommission_year: Option<u32>,
137 agent_ids: IndexSet<AgentID>,
138 processes: ProcessMap,
139 region_ids: IndexSet<RegionID>,
140 ) {
141 let asset_in = AssetRaw {
142 agent_id: "agent1".into(),
143 process_id: "process1".into(),
144 region_id: "GBR".into(),
145 capacity: Capacity(1.0),
146 commission_year: 2010,
147 max_decommission_year: max_decommission_year,
148 };
149 let asset_out = Asset::new_future_with_max_decommission(
150 "agent1".into(),
151 Rc::clone(processes.values().next().unwrap()),
152 "GBR".into(),
153 Capacity(1.0),
154 2010,
155 max_decommission_year,
156 )
157 .unwrap();
158 assert_equal(
159 read_assets_from_iter(iter::once(asset_in), &agent_ids, &processes, ®ion_ids)
160 .unwrap(),
161 iter::once(asset_out),
162 );
163 }
164
165 #[rstest]
166 #[case(AssetRaw { agent_id: "agent1".into(),
168 process_id: "process2".into(),
169 region_id: "GBR".into(),
170 capacity: Capacity(1.0),
171 commission_year: 2010,
172 max_decommission_year: None,
173 })]
174 #[case(AssetRaw { agent_id: "agent2".into(),
176 process_id: "process1".into(),
177 region_id: "GBR".into(),
178 capacity: Capacity(1.0),
179 commission_year: 2010,
180 max_decommission_year: None,
181 })]
182 #[case(AssetRaw { agent_id: "agent1".into(),
184 process_id: "process1".into(),
185 region_id: "FRA".into(),
186 capacity: Capacity(1.0),
187 commission_year: 2010,
188 max_decommission_year: None,
189 })]
190 #[case(AssetRaw { agent_id: "agent1".into(),
192 process_id: "process1".into(),
193 region_id: "GBR".into(),
194 capacity: Capacity(1.0),
195 commission_year: 2010,
196 max_decommission_year: Some(2005),
197 })]
198 fn test_read_assets_from_iter_invalid(
199 #[case] asset: AssetRaw,
200 agent_ids: IndexSet<AgentID>,
201 processes: ProcessMap,
202 region_ids: IndexSet<RegionID>,
203 ) {
204 assert!(
205 read_assets_from_iter(iter::once(asset), &agent_ids, &processes, ®ion_ids).is_err()
206 );
207 }
208}