-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementing statistics calculations. Still needs lots of testing.
- Loading branch information
Breno RdV
committed
Feb 15, 2024
1 parent
2e2bdcb
commit 915a2ff
Showing
21 changed files
with
743 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
Raccoon.Ninja.Domain.Core.Tests/Calculators/Abstractions/BaseCalculatorHandlerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using Raccoon.Ninja.Domain.Core.Calculators.Abstractions; | ||
using Raccoon.Ninja.Domain.Core.Calculators.Handlers; | ||
|
||
namespace Raccoon.Ninja.Domain.Core.Tests.Calculators.Abstractions; | ||
|
||
public class BaseCalculatorHandlerTests | ||
{ | ||
[Fact] | ||
public void BuildChain_ShouldReturnChain() | ||
{ | ||
// Arrange | ||
// Act | ||
var result = BaseCalculatorHandler.BuildChain(); | ||
|
||
// Assert | ||
result.Should().NotBeNull(); | ||
result.Should().BeOfType<AverageCalculator>(); | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
Raccoon.Ninja.Domain.Core.Tests/Calculators/Handlers/HbA1CCalculatorTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
using Raccoon.Ninja.Domain.Core.Calculators.Handlers; | ||
using Raccoon.Ninja.Domain.Core.Constants; | ||
using Raccoon.Ninja.Domain.Core.Enums; | ||
using Raccoon.Ninja.TestHelpers; | ||
|
||
namespace Raccoon.Ninja.Domain.Core.Tests.Calculators.Handlers; | ||
|
||
public class HbA1CCalculatorTests | ||
{ | ||
[Theory] | ||
[MemberData(nameof(TheoryGenerator.InvalidFloatListsWithNull), MemberType = typeof(TheoryGenerator))] | ||
public void Handle_WhenListIsInvalid_ShouldReturnError(IList<float> glucoseValues) | ||
{ | ||
// Arrange | ||
var calculator = new HbA1CCalculator(null); | ||
|
||
// Act | ||
var result = calculator.Handle(Generators.CalculationDataMockSingle(glucoseValues)); | ||
|
||
// Assert | ||
var status = result.Status; | ||
status.Success.Should().BeFalse(); | ||
status.FirstFailedStep.Should().Be(nameof(HbA1CCalculator)); | ||
status.Message.Should().Be("This calculation requires a valid average glucose value."); | ||
} | ||
|
||
[Fact] | ||
public void Handle_WhenListHasMoreThanExpected_ShouldReturnError() | ||
{ | ||
// Arrange | ||
var calculator = new HbA1CCalculator(null); | ||
|
||
const int actualReadingCount = HbA1CConstants.ReadingsIn115Days + 1; | ||
|
||
var glucoseValues = Generators.ListWithNumbers(actualReadingCount, 100f).ToList(); | ||
|
||
// Act | ||
var result = calculator.Handle(Generators.CalculationDataMockSingle(glucoseValues)); | ||
|
||
// Assert | ||
var status = result.Status; | ||
status.Success.Should().BeFalse(); | ||
status.FirstFailedStep.Should().Be(nameof(HbA1CCalculator)); | ||
status.Message.Should().Be($"Too many readings to calculate HbA1c reliably. Expected (max) 33120 but got {actualReadingCount}"); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(TheoryGenerator.PartiallyValidHb1AcDataSets), MemberType = typeof(TheoryGenerator))] | ||
public void Handle_WhenListHasLessThanExpectedReadings_ShouldReturnPartialSuccess(IList<float> glucoseValues, float expectedResult) | ||
{ | ||
// Arrange | ||
var calculator = new HbA1CCalculator(null); | ||
|
||
// Act | ||
var result = calculator.Handle(Generators.CalculationDataMockSingle(glucoseValues)); | ||
|
||
// Assert | ||
result.Status.Success.Should().BeTrue(); | ||
result.CurrentHbA1C.Should().NotBeNull(); | ||
result.CurrentHbA1C.Value.Should().Be(expectedResult); | ||
result.CurrentHbA1C.Status.Should().Be(HbA1CCalculationStatus.SuccessPartial); | ||
} | ||
|
||
[Theory] | ||
[MemberData(nameof(TheoryGenerator.ValidHb1AcDataSets), MemberType = typeof(TheoryGenerator))] | ||
public void CalculateHbA1c_WhenListHasExactNumberOfReadings_ShouldReturnSuccess(IList<float> glucoseValues, float expectedResult) | ||
{ | ||
// Arrange | ||
var calculator = new HbA1CCalculator(null); | ||
|
||
// Act | ||
var result = calculator.Handle(Generators.CalculationDataMockSingle(glucoseValues)); | ||
|
||
// Assert | ||
result.Status.Success.Should().BeTrue(); | ||
result.CurrentHbA1C.Should().NotBeNull(); | ||
result.CurrentHbA1C.Value.Should().Be(expectedResult); | ||
result.CurrentHbA1C.Status.Should().Be(HbA1CCalculationStatus.Success); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
Raccoon.Ninja.Domain.Core/Calculators/Abstractions/BaseCalculatorHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using Raccoon.Ninja.Domain.Core.Calculators.Handlers; | ||
|
||
namespace Raccoon.Ninja.Domain.Core.Calculators.Abstractions; | ||
|
||
/// <summary> | ||
/// Base class to handle the calculation of a specific metric. | ||
/// The overall result will be converted do a CosmosDb document | ||
/// and stored in the aggregation collection. | ||
/// </summary> | ||
/// <remarks> | ||
/// Why not use a simple function that calculates everything at once? | ||
/// - The idea is to have a pipeline of handlers, each one responsible for a specific calculation. | ||
/// - This way, we can easily add new calculations without changing the main class. | ||
/// - Also, we can easily test each calculation separately. | ||
/// </remarks> | ||
public abstract class BaseCalculatorHandler | ||
{ | ||
private readonly BaseCalculatorHandler _nextHandler; | ||
|
||
protected BaseCalculatorHandler(BaseCalculatorHandler nextHandler) | ||
{ | ||
_nextHandler = nextHandler; | ||
} | ||
|
||
protected virtual bool CanHandle(CalculationData data) | ||
{ | ||
return data.GlucoseValues is not null && data.GlucoseValues.Count > 0; | ||
} | ||
|
||
protected virtual CalculationData HandleError(CalculationData data) | ||
{ | ||
return data with | ||
{ | ||
Status = new CalculationDataStatus | ||
{ | ||
Message = "No glucose values were provided.", | ||
Success = false, | ||
FirstFailedStep = GetType().Name | ||
} | ||
}; | ||
} | ||
|
||
protected CalculationData HandleNext(CalculationData data) | ||
{ | ||
return _nextHandler is null | ||
? data | ||
: _nextHandler.Handle(data); | ||
} | ||
|
||
public abstract CalculationData Handle(CalculationData data); | ||
|
||
/// <summary> | ||
/// Build the default chain of calculations. | ||
/// </summary> | ||
/// <returns>First link in the chain.</returns> | ||
public static BaseCalculatorHandler BuildChain() | ||
{ | ||
// Last step of the chain | ||
var mageCalculator = new MageCalculator(null); | ||
var tirCalculator = new TimeInRangeCalculator(mageCalculator); | ||
var hbA1CCalculator = new HbA1CCalculator(tirCalculator); | ||
var glucoseVariabilityCalculator = new RangeCalculator(hbA1CCalculator); | ||
var medianCalculator = new MedianCalculator(glucoseVariabilityCalculator); | ||
var percentileCalculator = new PercentileCalculator(medianCalculator); | ||
var sdCalculator = new StandardDeviationCalculator(percentileCalculator); | ||
var cvCalculator = new CoefficientOfVariationCalculator(sdCalculator); | ||
var avgCalculator = new AverageCalculator(cvCalculator); | ||
//First step of the chain | ||
|
||
return avgCalculator; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
using Raccoon.Ninja.Domain.Core.Entities; | ||
|
||
namespace Raccoon.Ninja.Domain.Core.Calculators; | ||
|
||
public record CalculationData | ||
{ | ||
public IList<float> GlucoseValues { get; init; } | ||
public float Average { get; init; } | ||
public int Count => GlucoseValues?.Count ?? 0; | ||
public float Median { get; init; } | ||
public float Min { get; init; } | ||
public float Max { get; init; } | ||
public float Mage { get; init; } | ||
public float StandardDeviation { get; init; } | ||
public float CoefficientOfVariation { get; init; } | ||
public HbA1CCalculation CurrentHbA1C { get; init; } | ||
public HbA1CCalculation PreviousHbA1C { get; init; } | ||
|
||
public CalculationDataTimeInRange TimeInRange { get; init; } = new (); | ||
public CalculationDataPercentile Percentile { get; init; } = new (); | ||
public CalculationDataStatus Status { get; init; } = new (); | ||
} | ||
|
||
public record CalculationDataTimeInRange | ||
{ | ||
public float Low { get; init; } | ||
public float Normal { get; init; } | ||
public float High { get; init; } | ||
public float VeryHigh { get; init; } | ||
} | ||
|
||
public record CalculationDataPercentile | ||
{ | ||
public float P10 { get; init; } | ||
public float P25 { get; init; } | ||
public float P75 { get; init; } | ||
public float P90 { get; init; } | ||
public float Iqr { get; init; } | ||
} | ||
|
||
public record CalculationDataStatus | ||
{ | ||
public bool Success { get; init; } = true; | ||
public string FirstFailedStep { get; set; } | ||
public string Message { get; init; } | ||
} |
29 changes: 29 additions & 0 deletions
29
Raccoon.Ninja.Domain.Core/Calculators/Handlers/AverageCalculator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using Raccoon.Ninja.Domain.Core.Calculators.Abstractions; | ||
|
||
namespace Raccoon.Ninja.Domain.Core.Calculators.Handlers; | ||
|
||
/// <summary> | ||
/// Average Glucose: The average glucose level is a measure of central tendency that can be used to assess | ||
/// overall glucose control. | ||
/// </summary> | ||
public class AverageCalculator: BaseCalculatorHandler | ||
{ | ||
public AverageCalculator(BaseCalculatorHandler nextHandler) : base(nextHandler) | ||
{ | ||
} | ||
|
||
public override CalculationData Handle(CalculationData data) | ||
{ | ||
if (!CanHandle(data)) | ||
{ | ||
return HandleError(data); | ||
} | ||
|
||
var average = data.GlucoseValues.Average(); | ||
|
||
return HandleNext(data with | ||
{ | ||
Average = average, | ||
}); | ||
} | ||
} |
Oops, something went wrong.