Files
continuousimprovement/tests/Strata.ContinuousImprovement.Biz.Test.Integration/SnowflakeIntegrationTests/FlexibleExplorationSummaryTest.cs
T

207 lines
10 KiB
C#

using FluentAssertions;
using Strata.ContinuousImprovement.Biz.Exploration;
using Strata.ContinuousImprovement.Biz.Exploration.CaseTypeFamilies;
using Strata.ContinuousImprovement.Biz.Exploration.Filters;
using Strata.ContinuousImprovement.Biz.Test.Unit.Utilities;
using System.Diagnostics.CodeAnalysis;
namespace Strata.ContinuousImprovement.Biz.Test.Integration.SnowflakeIntegrationTests
{
[ExcludeFromCodeCoverage]
[TestFixture, Category("Integration"), Category("CCI")]
public class FlexibleExplorationSummaryTest : IntegrationTestBase
{
private static readonly Guid _configurationGuid = Guid.Parse("aa4cba4f-2506-462c-8e34-c45b21653988");
private static int _totalCaseTypeFamilyCount;
[OneTimeSetUp]
public async Task SetupExploration()
{
var explorationInfo = CreateExplorationInfo();
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
if (!summaries.Any())
throw new InvalidOperationException("No Case Type Family summaries returned for setup.");
_totalCaseTypeFamilyCount = summaries.Count();
}
[Test]
public async Task GetCaseTypeFamilySummaries_WhenNoFilters_ReturnsAll()
{
// arrange
var explorationInfo = CreateExplorationInfo();
// act
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
// assert
summaries.Should().HaveCount(_totalCaseTypeFamilyCount);
await VerifySummaries(summaries);
}
[TestCaseSource(nameof(ExclusionCriteriaTestCases))]
public async Task GetCaseTypeFamilySummaries_WithExclusions(ExclusionCriteria exclusions)
{
await RunExclusionSummaryTests(exclusions);
}
[Test]
public async Task GetCaseTypeFamilySummaries_WithFilters_ReturnsFiltered()
{
// arrange - filtering on mortality (IsSelected = true => MortalityRate = 1)
var booleanFilter = CreateBooleanFilter(ChipKey.Mortality);
var explorationInfo = CreateExplorationInfo(booleanFilter);
// act
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
// assert
summaries.Should().HaveCountGreaterThan(0);
summaries.Should().HaveCountLessThan(_totalCaseTypeFamilyCount, "Filters should reduce data returned");
// mortality rate only applies to included encounters
summaries.Where(x => x.Data.IncludedEncounterCount > 0).Should().OnlyContain(s => s.Data.MortalityRate == 1);
await VerifySummaries(summaries);
}
[Test]
public async Task GetCaseTypeFamilySummaries_WithFiltersAndExclusions_ReturnsFilteredAndExcluded()
{
// arrange - iqr + mad exclusion combo with a boolean filter
var booleanFilter = CreateBooleanFilter(ChipKey.Mortality);
var exclusions = new ExclusionCriteria
{
CostExclusionType = ExclusionCriteriaType.Iqr,
CostExclusionValue = 1.5,
LosExclusionType = ExclusionCriteriaType.Mad,
LosExclusionValue = 2
};
var explorationInfo = CreateExplorationInfo(booleanFilter, exclusions);
// act
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
// assert
summaries.Should().HaveCountGreaterThan(0);
summaries.Should().HaveCountLessThan(_totalCaseTypeFamilyCount);
// mortality rate only applies to included encounters
summaries.Where(x => x.Data.IncludedEncounterCount > 0).Should().OnlyContain(s => s.Data.MortalityRate == 1);
summaries.Should().Contain(s => s.Data.ExcludedEncountersAboveMedian > 0);
await VerifySummaries(summaries);
}
private FilterChipItem CreateDischargeDateFilter() => new()
{
ChipType = ChipType.DateRange,
Filters = [],
DateRange = [new DateTime(2024, 1, 1), new DateTime(2025, 1, 1)]
};
private FilterChipItem CreateBooleanFilter(ChipKey key)
{
if (key is ChipKey.Mortality)
{
return new FilterChipItem
{
ChipType = ChipType.Boolean,
GlobalId = "Mortality",
IsSelected = true, // determines true/false of filter
Key = ChipKey.Mortality,
Operator = OperatorType.In,
Filters = new List<TreeNode>()
{
new TreeNode
{
DisplayText = "Yes"
}},
};
}
throw new ArgumentException($"Unsupported boolean filter: {key}", nameof(key));
}
private ExplorationInfo CreateExplorationInfo(FilterChipItem? additionalFilter = null, ExclusionCriteria? exclusion = null)
{
var dischargeDateFilter = CreateDischargeDateFilter();
var filterChipItems = additionalFilter != null ?
new List<FilterChipItem> { dischargeDateFilter, additionalFilter }
: new List<FilterChipItem> { dischargeDateFilter };
return new ExplorationInfo
{
ConfigurationGuid = _configurationGuid,
ExclusionCriteria = exclusion ?? new ExclusionCriteria(),
Filters = new Biz.Exploration.Filters.Filter
{
DischargeDateStart = dischargeDateFilter.DateRange.First(),
DischargeDateEnd = dischargeDateFilter.DateRange.Last(),
FilterChipItems = filterChipItems
}
};
}
private async Task AssertExclusionsWereApplied(IEnumerable<CaseTypeFamilySummaryData> summaries)
{
summaries.Should().HaveCount(_totalCaseTypeFamilyCount);
summaries.Should().Contain(s => s.Data.ExcludedEncountersAboveMedian > 0);
}
private Task VerifySummaries(IEnumerable<CaseTypeFamilySummaryData> summaries)
{
summaries.Should().NotBeNull().And.NotBeEmpty();
summaries.Should().OnlyContain(s => !string.IsNullOrWhiteSpace(s.Key));
summaries.Should().OnlyContain(s => !string.IsNullOrWhiteSpace(s.Name));
summaries.Should().AllSatisfy(s => s.Key.Should().MatchRegex(@"^\d+-\d+$", "Key should follow format 'level-id'"));
summaries.Should().AllSatisfy(s => s.Data.Should().NotBeNull("Each summary should have data"));
summaries.Should().AllSatisfy(s => s.Data.Key.Should().MatchRegex(@"^\d+-\d+$", "Key should follow format 'level-id'"));
summaries.Should().AllSatisfy(s => s.Data.Id.Should().BeGreaterThan(0, "Each summary data should have a valid ID"));
summaries.Should().AllSatisfy(s => s.Data.TotalEncounters.Should().BeGreaterThanOrEqualTo(0, "Each summary data should have a valid TotalEncounters"));
summaries.Should().AllSatisfy(s => s.Data.TotalVariationEncounters.Should().BeGreaterThanOrEqualTo(0, "Each summary data should have a valid TotalVariationEncounters"));
summaries.Should().AllSatisfy(s => s.Data.IncludedEncounterCount.Should().BeGreaterThanOrEqualTo(0, "Each summary data should have a valid IncludedEncounterCount"));
summaries.Should().AllSatisfy(s => s.Data.IdentifiedVariation.Should().BeGreaterThanOrEqualTo(0, "Each summary data should have a valid IdentifiedVariation"));
summaries.Should().AllSatisfy(s => s.Data.MortalityRate.Should().BeInRange(0, 1, "MortalityRate should be between 0 and 1"));
summaries.Should().AllSatisfy(s => s.Data.MedianLos.Should().BeGreaterOrEqualTo(0, "MedianLos should be non-negative"));
summaries.Should().AllSatisfy(s => s.Data.MedianCost.Should().BeGreaterOrEqualTo(0, "MedianCost should be non-negative"));
summaries.Should().AllSatisfy(s => s.Data.IncludedEncountersBelowMedian.Should().BeGreaterOrEqualTo(0, "IncludedEncountersBelowMedian should be non-negative"));
summaries.Should().AllSatisfy(s => s.Data.IncludedEncountersAboveMedian.Should().BeGreaterOrEqualTo(0, "IncludedEncountersAboveMedian should be non-negative"));
summaries.Should().AllSatisfy(s => s.Data.IncludedVariation.Should().BeGreaterOrEqualTo(0, "IncludedVariation should be non-negative"));
return Verify(summaries, verifySettings)
.UseDirectory("Exploration")
.UseFileName(TestContext.CurrentContext.TestName());
}
private async Task RunExclusionSummaryTests(ExclusionCriteria exclusions)
{
// act
var explorationInfo = CreateExplorationInfo(null, exclusions);
var summaries = await ExplorationService.GetCaseTypeFamilySummariesAsync(explorationInfo, CancellationToken.None);
// assert
await AssertExclusionsWereApplied(summaries);
await VerifySummaries(summaries);
}
private static IEnumerable<TestCaseData> ExclusionCriteriaTestCases()
{
yield return new TestCaseData(
new ExclusionCriteria
{
CostExclusionType = ExclusionCriteriaType.Mad,
CostExclusionValue = 2,
LosExclusionType = ExclusionCriteriaType.Mad,
LosExclusionValue = 2
}
).SetName("GetCaseTypeFamilySummaries_WithMADExclusions_ReturnsWithExclusions");
yield return new TestCaseData(
new ExclusionCriteria
{
CostExclusionType = ExclusionCriteriaType.Iqr,
CostExclusionValue = 1.5,
LosExclusionType = ExclusionCriteriaType.Iqr,
LosExclusionValue = 1.5
}
).SetName("GetCaseTypeFamilySummaries_WithIQRExclusions_ReturnsWithExclusions");
}
}
}