Skip to main content

muse2/asset/
pool.rs

1//! Defines a data structure for representing the current active pool of assets.
2use super::{AssetID, AssetRef, AssetState, UserAsset};
3use itertools::Itertools;
4use log::warn;
5
6/// The active pool of [`super::Asset`]s
7#[derive(Default, derive_more::Deref)]
8pub struct AssetPool {
9    /// The pool of active assets, sorted by ID
10    #[deref]
11    assets: Vec<AssetRef>,
12    /// Next available asset ID number
13    next_id: u32,
14}
15
16impl AssetPool {
17    /// Create a new empty [`AssetPool`]
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    /// Commission new assets for the specified milestone year from the input data.
23    ///
24    /// Returns the newly commissioned assets.
25    pub fn commission_new(&mut self, year: u32, user_assets: &mut Vec<UserAsset>) -> &[AssetRef] {
26        let start = self.assets.len();
27        let to_commission = user_assets.extract_if(.., |asset| asset.commission_year <= year);
28
29        for asset in to_commission {
30            // Ignore assets that have already been decommissioned
31            if asset.max_decommission_year() <= year {
32                warn!(
33                    "User asset '{}' with commission year {} with maximum decommission year {} \
34                    was decommissioned before start of the simulation",
35                    asset.process_id(),
36                    asset.commission_year,
37                    asset.max_decommission_year
38                );
39                continue;
40            }
41
42            self.commission(asset.into());
43        }
44
45        &self.assets[start..]
46    }
47
48    /// Commission the specified asset
49    fn commission(&mut self, mut asset: AssetRef) {
50        asset.make_mut().commission(AssetID(self.next_id));
51        self.next_id += 1;
52        self.assets.push(asset);
53    }
54
55    /// Decommission old assets for the specified milestone year
56    pub fn decommission_old(&mut self, year: u32) {
57        self.assets
58            .extract_if(.., |asset| asset.max_decommission_year() <= year)
59            .for_each(|asset| {
60                asset.decommission("end of life");
61            });
62    }
63
64    /// Decommission mothballed assets if mothballed long enough
65    pub fn decommission_mothballed(&mut self, year: u32, mothball_years: u32) {
66        // Empty the Vec and reconstruct it with only the remaining units of the remaining assets
67        // after decommissioning. This sadly means we always allocate a new Vec, but modifying the
68        // Vec in place leads to uglier code and unnecessary deep clones of assets.
69        self.assets = std::mem::take(&mut self.assets)
70            .into_iter()
71            .filter_map(|asset| asset.with_decommission_mothballed(year, mothball_years))
72            .collect();
73    }
74
75    /// Mothball the specified assets if they are no longer in the active pool and put them back
76    /// again.
77    ///
78    /// # Arguments
79    ///
80    /// * `assets` - Assets to possibly mothball
81    /// * `year` - Mothball year
82    ///
83    /// # Panics
84    ///
85    /// Panics if any of the provided assets was never commissioned.
86    pub fn mothball_unretained<I>(&mut self, assets: I, year: u32)
87    where
88        I: IntoIterator<Item = AssetRef>,
89    {
90        for old_asset in assets {
91            let id = old_asset
92                .id()
93                .expect("Cannot mothball asset that has not been commissioned");
94
95            // Note that we cannot use a binary search here, as `self.assets` may have become
96            // unsorted by new assets added below
97            if let Some(new_asset) = self
98                .assets
99                .iter_mut()
100                .find(|asset| asset.id().unwrap() == id)
101            {
102                // At least some of the asset's units have made it back into the pool. Increase the
103                // capacity back to what it was before, with the unselected units set as mothballed.
104                let num_mothballed = old_asset
105                    .num_units()
106                    .checked_sub(new_asset.num_units())
107                    .expect("Number of units has increased");
108                *new_asset = old_asset.with_mothballed_units(num_mothballed, Some(year));
109            } else {
110                // None of this asset's units were selected. We mothball _all_ units and return to
111                // the pool.
112                let num_mothballed = old_asset.num_units();
113                self.assets
114                    .push(old_asset.with_mothballed_units(num_mothballed, Some(year)));
115            }
116        }
117        self.assets.sort();
118    }
119
120    /// Get an asset with the specified ID.
121    ///
122    /// # Returns
123    ///
124    /// An [`AssetRef`] if found, else `None`. The asset may not be found if it has already been
125    /// decommissioned.
126    pub fn get(&self, id: AssetID) -> Option<&AssetRef> {
127        // Assets are sorted by ID
128        let idx = self
129            .assets
130            .binary_search_by(|asset| match &asset.state {
131                AssetState::Commissioned { id: asset_id, .. } => asset_id.cmp(&id),
132                _ => panic!("Active pool should only contain commissioned assets"),
133            })
134            .ok()?;
135
136        Some(&self.assets[idx])
137    }
138
139    /// Return current active pool and clear
140    pub fn take(&mut self) -> Vec<AssetRef> {
141        std::mem::take(&mut self.assets)
142    }
143
144    /// Extend the active pool with Commissioned or Ready assets.
145    ///
146    /// Returns the newly commissioned assets (those that were in `Ready` state on entry).
147    pub fn extend<I>(&mut self, assets: I) -> &[AssetRef]
148    where
149        I: IntoIterator<Item = AssetRef>,
150    {
151        let first_new_id = self.next_id;
152
153        // Check all assets are either Commissioned or Ready, and, if the latter,
154        // then commission them
155        for asset in assets {
156            match &asset.state {
157                AssetState::Commissioned { .. } => {
158                    self.assets.push(asset);
159                }
160                AssetState::Ready { .. } => {
161                    self.commission(asset);
162                }
163                AssetState::Candidate => panic!(
164                    "Cannot extend asset pool with asset in state {}. Only assets in \
165                    Commissioned or Ready states are allowed.",
166                    asset.state
167                ),
168            }
169        }
170
171        // New assets may not have been sorted, but we need them sorted by ID
172        self.assets.sort();
173
174        // Sanity check: all assets should be unique
175        debug_assert_eq!(self.assets.iter().unique().count(), self.assets.len());
176
177        // Newly commissioned assets have IDs >= first_new_id. Since assets are sorted by ID,
178        // they are at the tail of the slice.
179        let new_start = self.assets.partition_point(|a| match &a.state {
180            AssetState::Commissioned { id, .. } => id.0 < first_new_id,
181            _ => panic!("Active pool should only contain commissioned assets"),
182        });
183        &self.assets[new_start..]
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::super::Asset;
190    use super::*;
191    use crate::asset::MothballEvent;
192    use crate::fixture::{asset, asset_divisible, process, process_parameter_map};
193    use crate::process::{Process, ProcessParameter};
194    use crate::units::{
195        Capacity, Dimensionless, MoneyPerActivity, MoneyPerCapacity, MoneyPerCapacityPerYear,
196    };
197    use itertools::{Itertools, assert_equal};
198    use rstest::{fixture, rstest};
199    use std::iter;
200    use std::rc::Rc;
201
202    #[fixture]
203    fn user_assets(mut process: Process) -> Vec<UserAsset> {
204        // Update process parameters (lifetime = 20 years)
205        let process_param = ProcessParameter {
206            capital_cost: MoneyPerCapacity(5.0),
207            fixed_operating_cost: MoneyPerCapacityPerYear(2.0),
208            variable_operating_cost: MoneyPerActivity(1.0),
209            lifetime: 20,
210            discount_rate: Dimensionless(0.9),
211        };
212        let process_parameter_map = process_parameter_map(process.regions.clone(), process_param);
213        process.parameters = process_parameter_map;
214
215        let rc_process = Rc::new(process);
216        [2020, 2010]
217            .map(|year| {
218                UserAsset::new(
219                    "agent1".into(),
220                    Rc::clone(&rc_process),
221                    "GBR".into(),
222                    Capacity(1.0),
223                    year,
224                    None,
225                )
226                .unwrap()
227            })
228            .into_iter()
229            .collect_vec()
230    }
231
232    #[rstest]
233    fn asset_pool_new() {
234        assert!(AssetPool::new().assets.is_empty());
235    }
236
237    #[rstest]
238    fn asset_pool_commission_new1(mut user_assets: Vec<UserAsset>) {
239        // Asset to be commissioned in this year
240        let mut asset_pool = AssetPool::new();
241        asset_pool.commission_new(2010, &mut user_assets);
242        assert_equal(asset_pool.iter(), iter::once(&asset_pool.assets[0]));
243    }
244
245    #[rstest]
246    fn asset_pool_commission_new2(mut user_assets: Vec<UserAsset>) {
247        // Commission year has passed
248        let mut asset_pool = AssetPool::new();
249        asset_pool.commission_new(2011, &mut user_assets);
250        assert_equal(asset_pool.iter(), iter::once(&asset_pool.assets[0]));
251    }
252
253    #[rstest]
254    fn asset_pool_commission_new3(mut user_assets: Vec<UserAsset>) {
255        // Nothing to commission for this year
256        let mut asset_pool = AssetPool::new();
257        asset_pool.commission_new(2000, &mut user_assets);
258        assert!(asset_pool.iter().next().is_none()); // no active assets
259    }
260
261    #[rstest]
262    fn asset_pool_commission_new_divisible(asset_divisible: Asset) {
263        let commission_year = asset_divisible.commission_year;
264        let mut asset_pool = AssetPool::new();
265        let mut user_assets = vec![asset_divisible.into()];
266        assert!(asset_pool.assets.is_empty());
267        asset_pool.commission_new(commission_year, &mut user_assets);
268        assert!(user_assets.is_empty());
269        assert_eq!(asset_pool.assets.len(), 1);
270        assert_eq!(asset_pool.next_id, 1);
271    }
272
273    #[rstest]
274    fn asset_pool_commission_already_decommissioned(asset: Asset) {
275        let year = asset.max_decommission_year();
276        let mut asset_pool = AssetPool::new();
277        assert!(asset_pool.assets.is_empty());
278        asset_pool.commission_new(year, &mut vec![asset.into()]);
279        assert!(asset_pool.assets.is_empty());
280    }
281
282    #[rstest]
283    fn asset_pool_decommission_old(mut user_assets: Vec<UserAsset>) {
284        let mut asset_pool = AssetPool::new();
285        asset_pool.commission_new(2020, &mut user_assets);
286        assert!(user_assets.is_empty());
287        assert_eq!(asset_pool.assets.len(), 2);
288
289        // should decommission first asset (lifetime == 5)
290        asset_pool.decommission_old(2030);
291        assert_eq!(asset_pool.assets.len(), 1);
292        assert_eq!(asset_pool.assets[0].commission_year, 2020);
293
294        // nothing to decommission
295        asset_pool.decommission_old(2032);
296        assert_eq!(asset_pool.assets.len(), 1);
297        assert_eq!(asset_pool.assets[0].commission_year, 2020);
298
299        // should decommission second asset
300        asset_pool.decommission_old(2040);
301        assert!(asset_pool.assets.is_empty());
302    }
303
304    #[rstest]
305    fn asset_pool_get(mut user_assets: Vec<UserAsset>) {
306        let mut asset_pool = AssetPool::new();
307        asset_pool.commission_new(2020, &mut user_assets);
308        assert_eq!(asset_pool.get(AssetID(0)), Some(&asset_pool.assets[0]));
309        assert_eq!(asset_pool.get(AssetID(1)), Some(&asset_pool.assets[1]));
310    }
311
312    #[rstest]
313    fn asset_pool_extend_empty(mut user_assets: Vec<UserAsset>) {
314        // Start with commissioned assets
315        let mut asset_pool = AssetPool::new();
316        asset_pool.commission_new(2020, &mut user_assets);
317        let original_count = asset_pool.assets.len();
318
319        // Extend with empty iterator
320        asset_pool.extend(Vec::<AssetRef>::new());
321
322        assert_eq!(asset_pool.assets.len(), original_count);
323    }
324
325    #[rstest]
326    fn asset_pool_extend_existing_assets(mut user_assets: Vec<UserAsset>) {
327        // Start with some commissioned assets
328        let mut asset_pool = AssetPool::new();
329        asset_pool.commission_new(2020, &mut user_assets);
330        assert_eq!(asset_pool.assets.len(), 2);
331        let existing_assets = asset_pool.take();
332
333        // Extend with the same assets (should maintain their IDs)
334        asset_pool.extend(existing_assets.clone());
335
336        assert_eq!(asset_pool.assets.len(), 2);
337        assert_eq!(asset_pool.assets[0].id(), Some(AssetID(0)));
338        assert_eq!(asset_pool.assets[1].id(), Some(AssetID(1)));
339    }
340
341    #[rstest]
342    fn asset_pool_extend_new_assets(mut user_assets: Vec<UserAsset>, process: Process) {
343        // Start with some commissioned assets
344        let mut asset_pool = AssetPool::new();
345        asset_pool.commission_new(2020, &mut user_assets);
346        let original_count = asset_pool.assets.len();
347
348        // Create new non-commissioned assets
349        let process_rc = Rc::new(process);
350        let new_assets = vec![
351            Asset::new_ready(
352                "agent2".into(),
353                Rc::clone(&process_rc),
354                "GBR".into(),
355                Capacity(1.5),
356                2015,
357            )
358            .unwrap()
359            .into(),
360            Asset::new_ready(
361                "agent3".into(),
362                Rc::clone(&process_rc),
363                "GBR".into(),
364                Capacity(2.5),
365                2020,
366            )
367            .unwrap()
368            .into(),
369        ];
370
371        asset_pool.extend(new_assets);
372
373        assert_eq!(asset_pool.assets.len(), original_count + 2);
374        // New assets should get IDs 2 and 3
375        assert_eq!(asset_pool.assets[original_count].id(), Some(AssetID(2)));
376        assert_eq!(asset_pool.assets[original_count + 1].id(), Some(AssetID(3)));
377        assert_eq!(
378            asset_pool.assets[original_count].agent_id(),
379            Some(&"agent2".into())
380        );
381        assert_eq!(
382            asset_pool.assets[original_count + 1].agent_id(),
383            Some(&"agent3".into())
384        );
385    }
386
387    #[rstest]
388    fn asset_pool_extend_mixed_assets(mut user_assets: Vec<UserAsset>, process: Process) {
389        // Start with some commissioned assets
390        let mut asset_pool = AssetPool::new();
391        asset_pool.commission_new(2020, &mut user_assets);
392
393        // Create a new non-commissioned asset
394        let new_asset = Asset::new_ready(
395            "agent_new".into(),
396            process.into(),
397            "GBR".into(),
398            Capacity(3.0),
399            2015,
400        )
401        .unwrap()
402        .into();
403
404        // Extend with just the new asset (not mixing with existing to avoid duplicates)
405        asset_pool.extend(vec![new_asset]);
406
407        assert_eq!(asset_pool.assets.len(), 3);
408        // Check that we have the original assets plus the new one
409        assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(0))));
410        assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(1))));
411        assert!(asset_pool.assets.iter().any(|a| a.id() == Some(AssetID(2))));
412        // Check that the new asset has the correct agent
413        assert!(
414            asset_pool
415                .assets
416                .iter()
417                .any(|a| a.agent_id() == Some(&"agent_new".into()))
418        );
419    }
420
421    #[rstest]
422    fn asset_pool_extend_maintains_sort_order(mut user_assets: Vec<UserAsset>, process: Process) {
423        // Start with some commissioned assets
424        let mut asset_pool = AssetPool::new();
425        asset_pool.commission_new(2020, &mut user_assets);
426
427        // Create new assets that would be out of order if added at the end
428        let process_rc = Rc::new(process);
429        let new_assets = vec![
430            Asset::new_ready(
431                "agent_high_id".into(),
432                Rc::clone(&process_rc),
433                "GBR".into(),
434                Capacity(1.0),
435                2010,
436            )
437            .unwrap()
438            .into(),
439            Asset::new_ready(
440                "agent_low_id".into(),
441                Rc::clone(&process_rc),
442                "GBR".into(),
443                Capacity(1.0),
444                2015,
445            )
446            .unwrap()
447            .into(),
448        ];
449
450        asset_pool.extend(new_assets);
451
452        // Check that assets are sorted by ID
453        let ids: Vec<u32> = asset_pool.iter().map(|a| a.id().unwrap().0).collect();
454        assert_equal(ids, 0..4);
455    }
456
457    #[rstest]
458    fn asset_pool_extend_no_duplicates_expected(mut user_assets: Vec<UserAsset>) {
459        // Start with some commissioned assets
460        let mut asset_pool = AssetPool::new();
461        asset_pool.commission_new(2020, &mut user_assets);
462        let original_count = asset_pool.assets.len();
463
464        // The extend method expects unique assets - adding duplicates would violate
465        // the debug assertion, so this test verifies the normal case
466        asset_pool.extend(Vec::new());
467
468        assert_eq!(asset_pool.assets.len(), original_count);
469        // Verify all assets are still unique (this is what the debug_assert checks)
470        assert_eq!(
471            asset_pool.assets.iter().unique().count(),
472            asset_pool.assets.len()
473        );
474    }
475
476    #[rstest]
477    fn asset_pool_extend_increments_next_id(mut user_assets: Vec<UserAsset>, process: Process) {
478        // Start with some commissioned assets
479        let mut asset_pool = AssetPool::new();
480        asset_pool.commission_new(2020, &mut user_assets);
481        assert_eq!(asset_pool.next_id, 2); // Should be 2 after commissioning 2 assets
482
483        // Create new non-commissioned assets
484        let process_rc = Rc::new(process);
485        let new_assets = vec![
486            Asset::new_ready(
487                "agent1".into(),
488                Rc::clone(&process_rc),
489                "GBR".into(),
490                Capacity(1.0),
491                2015,
492            )
493            .unwrap()
494            .into(),
495            Asset::new_ready(
496                "agent2".into(),
497                Rc::clone(&process_rc),
498                "GBR".into(),
499                Capacity(1.0),
500                2020,
501            )
502            .unwrap()
503            .into(),
504        ];
505
506        asset_pool.extend(new_assets);
507
508        // next_id should have incremented for each new asset
509        assert_eq!(asset_pool.next_id, 4);
510        assert_eq!(asset_pool.assets[2].id(), Some(AssetID(2)));
511        assert_eq!(asset_pool.assets[3].id(), Some(AssetID(3)));
512    }
513
514    #[rstest]
515    fn asset_pool_mothball_unretained(mut user_assets: Vec<UserAsset>) {
516        // Commission some assets
517        let mut asset_pool = AssetPool::new();
518        asset_pool.commission_new(2020, &mut user_assets);
519        assert_eq!(asset_pool.assets.len(), 2);
520
521        // Remove one asset from the active pool (simulating it being removed elsewhere)
522        let removed_asset = asset_pool.assets.remove(0);
523        assert_eq!(asset_pool.assets.len(), 1);
524
525        // Try to mothball both the removed asset (not in active) and an active asset
526        let assets_to_check = vec![removed_asset.clone(), asset_pool.assets[0].clone()];
527        asset_pool.mothball_unretained(assets_to_check, 2025);
528
529        // Only the removed asset should be mothballed (since it's not in active pool)
530        assert_eq!(asset_pool.assets.len(), 2); // And should be back into the pool
531        assert_equal(
532            asset_pool.assets[0].get_mothball_events().unwrap().iter(),
533            &[MothballEvent {
534                year: 2025,
535                num_units: 1,
536            }],
537        );
538    }
539
540    #[rstest]
541    fn asset_pool_decommission_unused(mut user_assets: Vec<UserAsset>) {
542        // Commission some assets
543        let mut asset_pool = AssetPool::new();
544        asset_pool.commission_new(2020, &mut user_assets);
545        assert_eq!(asset_pool.assets.len(), 2);
546
547        // Make an asset unused for a few years
548        let mothball_years: u32 = 10;
549        asset_pool.assets[0] = asset_pool[0]
550            .clone()
551            .with_mothballed_units(1, Some(2025 - mothball_years));
552
553        assert_equal(
554            asset_pool.assets[0].get_mothball_events().unwrap().iter(),
555            &[MothballEvent {
556                year: 2025 - mothball_years,
557                num_units: 1,
558            }],
559        );
560
561        // Decommission unused assets
562        asset_pool.decommission_mothballed(2025, mothball_years);
563
564        // Only the removed asset should be decommissioned (since it's not in active pool)
565        assert_eq!(asset_pool.assets.len(), 1); // Active pool unchanged
566    }
567
568    #[rstest]
569    fn asset_pool_decommission_if_not_active_none_active(mut user_assets: Vec<UserAsset>) {
570        // Commission some assets
571        let mut asset_pool = AssetPool::new();
572        asset_pool.commission_new(2020, &mut user_assets);
573        let all_assets = asset_pool.assets.clone();
574
575        // Clear the active pool (simulating all assets being removed)
576        asset_pool.assets.clear();
577
578        // Try to mothball the assets that are no longer active
579        asset_pool.mothball_unretained(all_assets.clone(), 2025);
580
581        // All assets should be mothballed
582        assert_eq!(asset_pool.assets.len(), 2);
583        assert_eq!(asset_pool.assets[0].id(), all_assets[0].id());
584        assert_equal(
585            asset_pool.assets[0].get_mothball_events().unwrap().iter(),
586            &[MothballEvent {
587                year: 2025,
588                num_units: 1,
589            }],
590        );
591        assert_eq!(asset_pool.assets[1].id(), all_assets[1].id());
592        assert_equal(
593            asset_pool.assets[1].get_mothball_events().unwrap().iter(),
594            &[MothballEvent {
595                year: 2025,
596                num_units: 1,
597            }],
598        );
599    }
600
601    #[rstest]
602    #[should_panic(expected = "Cannot mothball asset that has not been commissioned")]
603    fn asset_pool_decommission_if_not_active_non_commissioned_asset(process: Process) {
604        // Create a non-commissioned asset
605        let non_commissioned_asset = Asset::new_ready(
606            "agent_new".into(),
607            process.into(),
608            "GBR".into(),
609            Capacity(1.0),
610            2015,
611        )
612        .unwrap()
613        .into();
614
615        // This should panic because the asset was never commissioned
616        let mut asset_pool = AssetPool::new();
617        asset_pool.mothball_unretained(vec![non_commissioned_asset], 2025);
618    }
619
620    /// A commissioned divisible asset with three units.
621    #[fixture]
622    fn commissioned_divisible(mut asset_divisible: Asset) -> AssetRef {
623        asset_divisible.commission(AssetID(0));
624        assert_eq!(asset_divisible.num_units(), 3);
625        AssetRef::from(asset_divisible)
626    }
627
628    #[rstest]
629    fn asset_pool_mothball_unretained_partial(commissioned_divisible: AssetRef) {
630        // The full asset has three units; only two of them were retained in the pool
631        let full = commissioned_divisible;
632        let retained = full.clone().with_subset_of_units(2);
633
634        let mut asset_pool = AssetPool::new();
635        asset_pool.assets.push(retained);
636
637        asset_pool.mothball_unretained(vec![full], 2025);
638
639        // The asset is restored to its full capacity, with the unretained unit mothballed
640        assert_eq!(asset_pool.assets.len(), 1);
641        let asset = &asset_pool.assets[0];
642        assert_eq!(asset.num_units(), 3);
643        assert_eq!(asset.get_num_mothballed_units(), 1);
644        assert_equal(
645            asset.get_mothball_events().unwrap().iter(),
646            &[MothballEvent {
647                year: 2025,
648                num_units: 1,
649            }],
650        );
651    }
652
653    #[rstest]
654    fn asset_pool_decommission_mothballed_partial(commissioned_divisible: AssetRef) {
655        // Mothball one unit in 2010 and one in 2020, leaving one active
656        let asset = commissioned_divisible
657            .with_mothballed_units(1, Some(2010))
658            .with_mothballed_units(2, Some(2020));
659
660        let mut asset_pool = AssetPool::new();
661        asset_pool.assets.push(asset);
662
663        // Threshold of 2015: only the unit mothballed in 2010 is old enough to decommission
664        asset_pool.decommission_mothballed(2025, 10);
665
666        assert_eq!(asset_pool.assets.len(), 1);
667        let asset = &asset_pool.assets[0];
668        assert_eq!(asset.num_units(), 2);
669        assert_eq!(asset.get_num_mothballed_units(), 1);
670        assert_equal(
671            asset.get_mothball_events().unwrap().iter(),
672            &[MothballEvent {
673                year: 2020,
674                num_units: 1,
675            }],
676        );
677    }
678
679    #[rstest]
680    fn asset_pool_decommission_mothballed_removes_fully_mothballed(
681        commissioned_divisible: AssetRef,
682    ) {
683        // All three units mothballed long enough ago: the whole asset is removed from the pool
684        let asset = commissioned_divisible.with_mothballed_units(3, Some(2010));
685
686        let mut asset_pool = AssetPool::new();
687        asset_pool.assets.push(asset);
688
689        asset_pool.decommission_mothballed(2025, 10);
690
691        assert!(asset_pool.assets.is_empty());
692    }
693}