@@ -60,7 +60,7 @@ pub struct AppraisalOutput {
6060 /// The hypothetical unmet demand following investment in this asset
6161 pub unmet_demand : DemandMap ,
6262 /// The comparison metric to compare investment decisions
63- pub metric : Box < dyn MetricTrait > ,
63+ pub metric : Option < Box < dyn MetricTrait > > ,
6464 /// Capacity and activity coefficients used in the appraisal
6565 pub coefficients : Rc < ObjectiveCoefficients > ,
6666}
@@ -70,15 +70,15 @@ impl AppraisalOutput {
7070 pub fn new < T : MetricTrait > (
7171 asset : AssetRef ,
7272 results : ResultsMap ,
73- metric : T ,
73+ metric : Option < T > ,
7474 coefficients : Rc < ObjectiveCoefficients > ,
7575 ) -> Self {
7676 Self {
7777 asset,
7878 capacity : results. capacity ,
7979 activity : results. activity ,
8080 unmet_demand : results. unmet_demand ,
81- metric : Box :: new ( metric ) ,
81+ metric : metric . map ( |m| Box :: new ( m ) as Box < dyn MetricTrait > ) ,
8282 coefficients,
8383 }
8484 }
@@ -95,18 +95,20 @@ impl AppraisalOutput {
9595 self . is_valid( ) && other. is_valid( ) ,
9696 "Cannot compare non-valid outputs"
9797 ) ;
98- assert ! (
99- !( self . metric. value( ) . is_nan( ) || other. metric. value( ) . is_nan( ) ) ,
100- "Appraisal metric cannot be NaN"
101- ) ;
102- self . metric . compare ( other. metric . as_ref ( ) )
98+
99+ // We've already checked the metrics aren't `None` in `is_valid`
100+ self . metric
101+ . as_ref ( )
102+ . unwrap ( )
103+ . compare ( other. metric . as_ref ( ) . unwrap ( ) . as_ref ( ) )
103104 }
104105
105106 /// Whether this [`AppraisalOutput`] is a valid output.
106107 ///
107- /// Specifically, it checks whether the calculated capacity is greater than zero.
108+ /// Specifically, it checks whether the metric is a valid value (not `None`) and that the
109+ /// calculated capacity is greater than zero.
108110 pub fn is_valid ( & self ) -> bool {
109- self . capacity . total_capacity ( ) > Capacity ( 0.0 )
111+ self . metric . is_some ( ) && self . capacity . total_capacity ( ) > Capacity ( 0.0 )
110112 }
111113}
112114
@@ -275,7 +277,7 @@ fn calculate_lcox(
275277 Ok ( AppraisalOutput :: new (
276278 asset. clone ( ) ,
277279 results,
278- LCOXMetric :: new ( cost_index) ,
280+ Some ( LCOXMetric :: new ( cost_index) ) ,
279281 coefficients. clone ( ) ,
280282 ) )
281283}
@@ -319,7 +321,7 @@ fn calculate_npv(
319321 Ok ( AppraisalOutput :: new (
320322 asset. clone ( ) ,
321323 results,
322- NPVMetric :: new ( profitability_index) ,
324+ Some ( NPVMetric :: new ( profitability_index) ) ,
323325 coefficients. clone ( ) ,
324326 ) )
325327}
@@ -582,7 +584,7 @@ mod tests {
582584 coefficients : objective_coeffs ( ) ,
583585 activity : IndexMap :: new ( ) ,
584586 unmet_demand : IndexMap :: new ( ) ,
585- metric,
587+ metric : Some ( metric ) ,
586588 } )
587589 . collect ( )
588590 }
@@ -610,9 +612,9 @@ mod tests {
610612 appraisal_outputs_with_investment_priority_invariant_to_assets ( metrics, & asset) ;
611613 sort_appraisal_outputs_by_investment_priority ( & mut outputs) ;
612614
613- assert_approx_eq ! ( f64 , outputs[ 0 ] . metric. value( ) , 3.0 ) ; // Best (lowest)
614- assert_approx_eq ! ( f64 , outputs[ 1 ] . metric. value( ) , 5.0 ) ;
615- assert_approx_eq ! ( f64 , outputs[ 2 ] . metric. value( ) , 7.0 ) ; // Worst (highest)
615+ assert_approx_eq ! ( f64 , outputs[ 0 ] . metric. as_ref ( ) . unwrap ( ) . value( ) , 3.0 ) ; // Best (lowest)
616+ assert_approx_eq ! ( f64 , outputs[ 1 ] . metric. as_ref ( ) . unwrap ( ) . value( ) , 5.0 ) ;
617+ assert_approx_eq ! ( f64 , outputs[ 2 ] . metric. as_ref ( ) . unwrap ( ) . value( ) , 7.0 ) ; // Worst (highest)
616618 }
617619
618620 /// Test sorting by NPV profitability index when invariant to asset properties
@@ -638,9 +640,9 @@ mod tests {
638640 sort_appraisal_outputs_by_investment_priority ( & mut outputs) ;
639641
640642 // Higher profitability index is better, so should be sorted: 3.0, 2.0, 1.5
641- assert_approx_eq ! ( f64 , outputs[ 0 ] . metric. value( ) , 3.0 ) ; // Best (highest PI)
642- assert_approx_eq ! ( f64 , outputs[ 1 ] . metric. value( ) , 2.0 ) ;
643- assert_approx_eq ! ( f64 , outputs[ 2 ] . metric. value( ) , 1.5 ) ; // Worst (lowest PI)
643+ assert_approx_eq ! ( f64 , outputs[ 0 ] . metric. as_ref ( ) . unwrap ( ) . value( ) , 3.0 ) ; // Best (highest PI)
644+ assert_approx_eq ! ( f64 , outputs[ 1 ] . metric. as_ref ( ) . unwrap ( ) . value( ) , 2.0 ) ;
645+ assert_approx_eq ! ( f64 , outputs[ 2 ] . metric. as_ref ( ) . unwrap ( ) . value( ) , 1.5 ) ; // Worst (lowest PI)
644646 }
645647
646648 /// Test that NPV metrics with zero annual fixed cost are prioritised above all others
@@ -670,9 +672,9 @@ mod tests {
670672 sort_appraisal_outputs_by_investment_priority ( & mut outputs) ;
671673
672674 // Zero AFC should be first despite lower absolute surplus value
673- assert_approx_eq ! ( f64 , outputs[ 0 ] . metric. value( ) , 50.0 ) ; // Zero AFC (uses surplus)
674- assert_approx_eq ! ( f64 , outputs[ 1 ] . metric. value( ) , 10.0 ) ; // PI = 1000/100
675- assert_approx_eq ! ( f64 , outputs[ 2 ] . metric. value( ) , 10.0 ) ; // PI = 500/50
675+ assert_approx_eq ! ( f64 , outputs[ 0 ] . metric. as_ref ( ) . unwrap ( ) . value( ) , 50.0 ) ; // Zero AFC (uses surplus)
676+ assert_approx_eq ! ( f64 , outputs[ 1 ] . metric. as_ref ( ) . unwrap ( ) . value( ) , 10.0 ) ; // PI = 1000/100
677+ assert_approx_eq ! ( f64 , outputs[ 2 ] . metric. as_ref ( ) . unwrap ( ) . value( ) , 10.0 ) ; // PI = 500/50
676678 }
677679
678680 /// Test that mixing LCOX and NPV metrics causes a runtime panic during comparison
@@ -886,7 +888,11 @@ mod tests {
886888 sort_appraisal_outputs_by_investment_priority ( & mut outputs) ;
887889
888890 // non-commissioned asset prioritised because it has a slightly better metric
889- assert_approx_eq ! ( f64 , outputs[ 0 ] . metric. value( ) , best_metric_value) ;
891+ assert_approx_eq ! (
892+ f64 ,
893+ outputs[ 0 ] . metric. as_ref( ) . unwrap( ) . value( ) ,
894+ best_metric_value
895+ ) ;
890896 }
891897
892898 /// Test that appraisal outputs with zero capacity are filtered out during sorting.
@@ -908,7 +914,7 @@ mod tests {
908914 coefficients : objective_coeffs ( ) ,
909915 activity : IndexMap :: new ( ) ,
910916 unmet_demand : IndexMap :: new ( ) ,
911- metric,
917+ metric : Some ( metric ) ,
912918 } )
913919 . collect ( ) ;
914920
0 commit comments