muse2/input/
asset.rs

1//! Code for reading [Asset]s from a CSV file.
2use super::*;
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};
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(Deserialize, PartialEq)]
19struct AssetRaw {
20    process_id: String,
21    region_id: String,
22    agent_id: String,
23    capacity: Capacity,
24    commission_year: u32,
25}
26
27/// Read assets CSV file from model directory.
28///
29/// # Arguments
30///
31/// * `model_dir` - Folder containing model configuration files
32/// * `agent_ids` - All possible process IDs
33/// * `processes` - The model's processes
34/// * `region_ids` - All possible region IDs
35///
36/// # Returns
37///
38/// A `HashMap` containing assets grouped by agent ID.
39pub fn read_assets(
40    model_dir: &Path,
41    agent_ids: &IndexSet<AgentID>,
42    processes: &ProcessMap,
43    region_ids: &IndexSet<RegionID>,
44) -> Result<Vec<Asset>> {
45    let file_path = model_dir.join(ASSETS_FILE_NAME);
46    let assets_csv = read_csv(&file_path)?;
47    read_assets_from_iter(assets_csv, agent_ids, processes, region_ids)
48        .with_context(|| input_err_msg(&file_path))
49}
50
51/// Process assets from an iterator.
52///
53/// # Arguments
54///
55/// * `iter` - Iterator of `AssetRaw`s
56/// * `agent_ids` - All possible process IDs
57/// * `processes` - The model's processes
58/// * `region_ids` - All possible region IDs
59///
60/// # Returns
61///
62/// A [`Vec`] of [`Asset`]s or an error.
63fn read_assets_from_iter<I>(
64    iter: I,
65    agent_ids: &IndexSet<AgentID>,
66    processes: &ProcessMap,
67    region_ids: &IndexSet<RegionID>,
68) -> Result<Vec<Asset>>
69where
70    I: Iterator<Item = AssetRaw>,
71{
72    iter.map(|asset| -> Result<_> {
73        let agent_id = agent_ids.get_id(&asset.agent_id)?;
74        let process = processes
75            .get(asset.process_id.as_str())
76            .with_context(|| format!("Invalid process ID: {}", &asset.process_id))?;
77        let region_id = region_ids.get_id(&asset.region_id)?;
78
79        Asset::new(
80            agent_id.clone(),
81            Rc::clone(process),
82            region_id.clone(),
83            asset.capacity,
84            asset.commission_year,
85        )
86    })
87    .try_collect()
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::fixture::{processes, region_ids};
94
95    use itertools::assert_equal;
96    use rstest::{fixture, rstest};
97    use std::iter;
98
99    #[fixture]
100    fn agent_ids() -> IndexSet<AgentID> {
101        IndexSet::from(["agent1".into()])
102    }
103
104    #[rstest]
105    fn test_read_assets_from_iter_valid(
106        agent_ids: IndexSet<AgentID>,
107        processes: ProcessMap,
108        region_ids: IndexSet<RegionID>,
109    ) {
110        let asset_in = AssetRaw {
111            agent_id: "agent1".into(),
112            process_id: "process1".into(),
113            region_id: "GBR".into(),
114            capacity: Capacity(1.0),
115            commission_year: 2010,
116        };
117        let asset_out = Asset::new(
118            "agent1".into(),
119            Rc::clone(processes.values().next().unwrap()),
120            "GBR".into(),
121            Capacity(1.0),
122            2010,
123        )
124        .unwrap();
125        assert_equal(
126            read_assets_from_iter(iter::once(asset_in), &agent_ids, &processes, &region_ids)
127                .unwrap(),
128            iter::once(asset_out),
129        );
130    }
131
132    #[rstest]
133    #[case(AssetRaw { // Bad process ID
134            agent_id: "agent1".into(),
135            process_id: "process2".into(),
136            region_id: "GBR".into(),
137            capacity: Capacity(1.0),
138            commission_year: 2010,
139        })]
140    #[case(AssetRaw { // Bad agent ID
141            agent_id: "agent2".into(),
142            process_id: "process1".into(),
143            region_id: "GBR".into(),
144            capacity: Capacity(1.0),
145            commission_year: 2010,
146        })]
147    #[case(AssetRaw { // Bad region ID: not in region_ids
148            agent_id: "agent1".into(),
149            process_id: "process1".into(),
150            region_id: "FRA".into(),
151            capacity: Capacity(1.0),
152            commission_year: 2010,
153        })]
154    fn test_read_assets_from_iter_invalid(
155        #[case] asset: AssetRaw,
156        agent_ids: IndexSet<AgentID>,
157        processes: ProcessMap,
158        region_ids: IndexSet<RegionID>,
159    ) {
160        assert!(
161            read_assets_from_iter(iter::once(asset), &agent_ids, &processes, &region_ids).is_err()
162        );
163    }
164}