Template
207 lines
10 KiB
C#
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");
|
|
}
|
|
}
|
|
} |