Skip to content

Commit 7c4e761

Browse files
authored
fixes issue with sometimes swapped 25th and 75th percentile in UI (#1315)
1 parent 0fea356 commit 7c4e761

12 files changed

Lines changed: 72 additions & 25 deletions

File tree

HEC.FDA.Model/metrics/ScenarioResults.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,13 @@ public double ConsequencesExceededWithProbabilityQ(double exceedanceProbability,
140140
ConsequenceType consequenceType = ConsequenceType.Damage,
141141
RiskType riskType = RiskType.Total)
142142
{
143+
double nonExceedanceProbability = 1 - exceedanceProbability;
143144
double consequenceValue = 0;
144145
foreach (ImpactAreaScenarioResults impactAreaScenarioResults in ResultsList)
145146
{
146147
consequenceValue += impactAreaScenarioResults.ConsequenceResults.ConsequenceResultList
147148
.FilterByCategories(damageCategory, assetCategory, impactAreaID, consequenceType, riskType)
148-
.Sum((x) => x.ConsequenceHistogram.InverseCDF(exceedanceProbability));
149+
.Sum((x) => x.ConsequenceHistogram.InverseCDF(nonExceedanceProbability));
149150
}
150151
return consequenceValue;
151152
}

HEC.FDA.ModelTest/unittests/AlternativeTest.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

HEC.FDA.ViewModel/AlternativeComparisonReport/Results/AggregatedAALLSummaryRowItem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ public class AggregatedAALLSummaryRowItem
1515
public double WithProjEAD { get; set; }
1616
[DisplayAsColumn("Mean AALL Reduced")]
1717
public double EADDamageReduced { get; set; }
18-
[DisplayAsColumn("25th Percentile AALL Reduced")] //This is intentionally swapped 1-x
18+
[DisplayAsColumn("25th Percentile AALL Reduced")] // Point75 = ExceededWithProbabilityQ(.75) = InverseCDF(1-.75) = 25th percentile
1919
public double Point75 { get; set; }
2020
[DisplayAsColumn("50th Percentile AALL Reduced")]
2121
public double Point5 { get; set; }
22-
[DisplayAsColumn("75th Percentile AALL Reduced")] //This is intentionally swapped 1-x
22+
[DisplayAsColumn("75th Percentile AALL Reduced")] // Point25 = ExceededWithProbabilityQ(.25) = InverseCDF(1-.25) = 75th percentile
2323
public double Point25 { get; set; }
2424

2525

HEC.FDA.ViewModel/AlternativeComparisonReport/Results/AggregatedEADSummaryRowItem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ public class AggregatedEADSummaryRowItem
1717
public double WithProjEAD { get; set; }
1818
[DisplayAsColumn("Mean EAD Reduced")]
1919
public double EADDamageReduced { get; set; }
20-
[DisplayAsColumn("25th Percentile EAD Reduced")] //This is intentionally swapped 1-x
20+
[DisplayAsColumn("25th Percentile EAD Reduced")] // Point75 = ExceededWithProbabilityQ(.75) = InverseCDF(1-.75) = 25th percentile
2121
public double Point75 { get; set; }
2222
[DisplayAsColumn("50th Percentile EAD Reduced")]
2323
public double Point5 { get; set; }
24-
[DisplayAsColumn("75th Percentile EAD Reduced")] //This is intentionally swapped 1-x
24+
[DisplayAsColumn("75th Percentile EAD Reduced")] // Point25 = ExceededWithProbabilityQ(.25) = InverseCDF(1-.25) = 75th percentile
2525
public double Point25 { get; set; }
2626

2727

HEC.FDA.ViewModel/AlternativeComparisonReport/Results/AggregatedEqadSummaryRowItem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ public class AggregatedEqadSummaryRowItem
1616
public double WithProjEqad { get; set; }
1717
[DisplayAsColumn("Mean EqAD Reduced")]
1818
public double EqadReduced { get; set; }
19-
[DisplayAsColumn("25th Percentile EqAD Reduced")] // This is intentionally swapped 1-x
19+
[DisplayAsColumn("25th Percentile EqAD Reduced")] // Point75 = ExceededWithProbabilityQ(.75) = InverseCDF(1-.75) = 25th percentile
2020
public double Point75 { get; set; }
2121
[DisplayAsColumn("50th Percentile EqAD Reduced")]
2222
public double Point5 { get; set; }
23-
[DisplayAsColumn("75th Percentile EqAD Reduced")] // This is intentionally swapped 1-x
23+
[DisplayAsColumn("75th Percentile EqAD Reduced")] // Point25 = ExceededWithProbabilityQ(.25) = InverseCDF(1-.25) = 75th percentile
2424
public double Point25 { get; set; }
2525
public AggregatedEqadSummaryRowItem(string impactArea, string withoutName, double withoutEqad, string withProjName, double withProjEqad,
2626
double eqadReduced, double point75, double point5, double point25)

HEC.FDA.ViewModel/AlternativeComparisonReport/Results/EADSummaryRowItem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ public class EADSummaryRowItem
2121
public double WithProjEAD { get; set; }
2222
[DisplayAsColumn("Mean EAD Reduced")]
2323
public double EADReduced { get; set; }
24-
[DisplayAsColumn("25th Percentile EAD Reduced")] //This is intentionally swapped 1-x
24+
[DisplayAsColumn("25th Percentile EAD Reduced")] // Point75 = ExceededWithProbabilityQ(.75) = InverseCDF(1-.75) = 25th percentile
2525
public double Point75 { get; set; }
2626
[DisplayAsColumn("50th Percentile EAD Reduced")]
2727
public double Point5 { get; set; }
28-
[DisplayAsColumn("75th Percentile EAD Reduced")] //This is intentionally swapped 1-x
28+
[DisplayAsColumn("75th Percentile EAD Reduced")] // Point25 = ExceededWithProbabilityQ(.25) = InverseCDF(1-.25) = 75th percentile
2929
public double Point25 { get; set; }
3030

3131
public EADSummaryRowItem(string impactArea, string damcat, string assetcat, string withoutName, double withoutEad, string withProjName, double withProjEqad, double eadReduced, double point75, double point5, double point25 )

HEC.FDA.ViewModel/AlternativeComparisonReport/Results/EqadSummaryRowItem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ public class EqadSummaryRowItem
2020
public double WithProjEqad { get; set; }
2121
[DisplayAsColumn("Mean EqAD Reduced")]
2222
public double EqadReduced { get; set; }
23-
[DisplayAsColumn("25th Percentile EqAD Reduced")]// This is intentionally swapped 1-x
23+
[DisplayAsColumn("25th Percentile EqAD Reduced")] // Point75 = ExceededWithProbabilityQ(.75) = InverseCDF(1-.75) = 25th percentile
2424
public double Point75 { get; set; }
2525
[DisplayAsColumn("50th Percentile EqAD Reduced")]
2626
public double Point5 { get; set; }
27-
[DisplayAsColumn("75th Percentile EqAD Reduced")] // This is intentionally swapped 1-x
27+
[DisplayAsColumn("75th Percentile EqAD Reduced")] // Point25 = ExceededWithProbabilityQ(.25) = InverseCDF(1-.25) = 75th percentile
2828
public double Point25 { get; set; }
2929
public EqadSummaryRowItem(string impactArea, string damcat, string assetcat,string withoutName, double withoutEqad, string withProjName, double withProjEqad,
3030
double eqadReduced, double point75, double point5, double point25)

HEC.FDA.ViewModel/Alternatives/Results/BatchCompute/AlternativeDamageRowItem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ public class AlternativeDamageRowItem:BaseViewModel
2626
public int PeriodOfAnalysis { get; set; }
2727
[DisplayAsColumn("Mean EqAD")]
2828
public double Mean { get; set; }
29-
[DisplayAsColumn("25th Percentile EqAD")] //This is intentionally swapped 1-x
29+
[DisplayAsColumn("25th Percentile EqAD")] // Point75 = ExceededWithProbabilityQ(.75) = InverseCDF(1-.75) = 25th percentile
3030
public double Point75 { get; set; }
3131
[DisplayAsColumn("50th Percentile EqAD")]
3232
public double Point5 { get; set; }
33-
[DisplayAsColumn("75th Percentile EqAD")]//This is intentionally swapped 1-x
33+
[DisplayAsColumn("75th Percentile EqAD")] // Point25 = ExceededWithProbabilityQ(.25) = InverseCDF(1-.25) = 75th percentile
3434
public double Point25 { get; set; }
3535
[DisplayAsColumn("Risk Type")]
3636
public string RiskType { get; set; }

HEC.FDA.ViewModel/Alternatives/Results/DamageWithUncertaintyVM.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ private static string ProbabilityFormatter(double d)
230230

231231
private void LoadData(AlternativeResults scenarioResults, DamageMeasureYear damageMeasureYear)
232232
{
233-
List<double> xVals = new() { .25, .5, .75 };
233+
List<double> xVals = new() { .75, .5, .25 };
234234
List<string> xValNames = new() { "25%", "50%", "75%" };
235235
List<double> yVals = LoadYData(xVals, scenarioResults, damageMeasureYear);
236236

HEC.FDA.ViewModel/ImpactAreaScenario/Results/DamageWithUncertaintyVM.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ public DamageWithUncertaintyVM(
7070

7171
List<double> qValues = new()
7272
{
73-
scenarioResults.ConsequencesExceededWithProbabilityQ(.25, impactAreaID, consequenceType: _uncertaintyControlConfig.ConsequenceType),
73+
scenarioResults.ConsequencesExceededWithProbabilityQ(.75, impactAreaID, consequenceType: _uncertaintyControlConfig.ConsequenceType),
7474
scenarioResults.ConsequencesExceededWithProbabilityQ(.5, impactAreaID, consequenceType: _uncertaintyControlConfig.ConsequenceType),
75-
scenarioResults.ConsequencesExceededWithProbabilityQ(.75, impactAreaID, consequenceType: _uncertaintyControlConfig.ConsequenceType)
75+
scenarioResults.ConsequencesExceededWithProbabilityQ(.25, impactAreaID, consequenceType: _uncertaintyControlConfig.ConsequenceType)
7676
};
7777
LoadTableValues(qValues);
7878

@@ -187,7 +187,7 @@ private void LoadTableValuesGrouped(ScenarioResults scenarioResults, int impactA
187187
{
188188
var riskTypes = new[] { ("Fail", RiskType.Fail), ("Non-Fail", RiskType.Non_Fail), ("Total", RiskType.Total) };
189189
var quartileNames = new[] { "25%", "50%", "75%" };
190-
var probabilities = new[] { 0.25, 0.5, 0.75 };
190+
var probabilities = new[] { 0.75, 0.5, 0.25 };
191191

192192
foreach (var (riskTypeName, riskType) in riskTypes)
193193
{

0 commit comments

Comments
 (0)