@@ -387,5 +387,51 @@ public void LifeLossOnly_EqadResultsIsNull()
387387 Assert . NotNull ( results ) ;
388388 Assert . Empty ( results . EqadResults . ConsequenceResultList ) ;
389389 }
390+
391+ /// <summary>
392+ /// Regression test for the exceedance/non-exceedance quartile swap bug.
393+ /// ExceededWithProbabilityQ(0.75) should return the 25th percentile (small value),
394+ /// and ExceededWithProbabilityQ(0.25) should return the 75th percentile (large value).
395+ /// Previously, ScenarioResults.ConsequencesExceededWithProbabilityQ was missing the
396+ /// 1-p conversion, causing quartiles to be swapped in certain code paths.
397+ /// </summary>
398+ [ Fact ]
399+ public void ExceededWithProbabilityQ_HigherExceedance_ReturnsLowerValue ( )
400+ {
401+ ConvergenceCriteria cc = new ConvergenceCriteria ( minIterations : 100 , maxIterations : 100 ) ;
402+
403+ // Create a histogram with values [1000, 1001, ..., 1099] — has clear spread for distinct quartiles
404+ var damageHist = new DynamicHistogram ( Enumerable . Range ( 1000 , 100 ) . Select ( i => ( double ) i ) . ToList ( ) , cc ) ;
405+ var damage = new AggregatedConsequencesBinned ( damCat , assetCat , damageHist , impactAreaID , ConsequenceType . Damage , RiskType . Fail ) ;
406+
407+ var impactArea = new ImpactAreaScenarioResults ( impactAreaID ) ;
408+ impactArea . ConsequenceResults . AddExistingConsequenceResultObject ( damage ) ;
409+ var scenarioResults = new ScenarioResults ( ) ;
410+ scenarioResults . AddResults ( impactArea ) ;
411+
412+ // ScenarioResults path (the fixed method)
413+ double exceededWith25Pct = scenarioResults . ConsequencesExceededWithProbabilityQ ( 0.25 , impactAreaID , damCat , assetCat ) ;
414+ double exceededWith75Pct = scenarioResults . ConsequencesExceededWithProbabilityQ ( 0.75 , impactAreaID , damCat , assetCat ) ;
415+
416+ // Value exceeded with 25% probability = 75th percentile = LARGE
417+ // Value exceeded with 75% probability = 25th percentile = SMALL
418+ Assert . True ( exceededWith25Pct > exceededWith75Pct ,
419+ $ "The value exceeded with 25% probability ({ exceededWith25Pct } ) should be greater than " +
420+ $ "the value exceeded with 75% probability ({ exceededWith75Pct } ). " +
421+ $ "If these are swapped, the 1-p conversion in ScenarioResults may be missing.") ;
422+
423+ // Also verify via AlternativeResults (ScenariosAreIdentical path) for consistency
424+ AlternativeResults altResults = Alternative . AnnualizationCompute (
425+ discountRate : 0.0275 , periodOfAnalysis : 50 , alternativeResultsID : alternativeID ,
426+ computedResultsBaseYear : scenarioResults , computedResultsFutureYear : null ,
427+ baseYear : 2023 , futureYear : 2072 ) ;
428+
429+ double altExceededWith25Pct = altResults . EqadExceededWithProbabilityQ ( 0.25 , impactAreaID , damCat , assetCat ) ;
430+ double altExceededWith75Pct = altResults . EqadExceededWithProbabilityQ ( 0.75 , impactAreaID , damCat , assetCat ) ;
431+
432+ Assert . True ( altExceededWith25Pct > altExceededWith75Pct ,
433+ $ "AlternativeResults (ScenariosAreIdentical path): value exceeded with 25% probability ({ altExceededWith25Pct } ) " +
434+ $ "should be greater than value exceeded with 75% probability ({ altExceededWith75Pct } ).") ;
435+ }
390436 }
391437}
0 commit comments