From 04ee1defbb80cde1e993372af962d98b7cec294d Mon Sep 17 00:00:00 2001 From: PabloDinella Date: Tue, 10 Dec 2024 15:20:37 -0300 Subject: [PATCH 1/5] Now the calculations accepts multiple hosting location changes without a pause in between --- .../PolicyEvaluation/ReferralCalculations.cs | 44 ++--- .../CreateChildLocationBasedTimeline.cs | 166 +++++++++++------- 2 files changed, 125 insertions(+), 85 deletions(-) diff --git a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs index 668ca3a6..73895df9 100644 --- a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs +++ b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs @@ -308,14 +308,8 @@ DateOnly today .Where(missingDueDate => !fva.ExemptedRequirements.Any(exempted => exempted.RequirementName == monitoringRequirement.ActionName - && ( - !exempted.DueDate.HasValue - || exempted.DueDate == missingDueDate - ) - && ( - exempted.ExemptionExpiresAt == null - || exempted.ExemptionExpiresAt > today - ) + && (!exempted.DueDate.HasValue || exempted.DueDate == missingDueDate) + && (exempted.ExemptionExpiresAt == null || exempted.ExemptionExpiresAt > today) ) ) .Select(missingDueDate => new MissingArrangementRequirement( @@ -366,10 +360,7 @@ DateOnly today !iva.ExemptedRequirements.Any(exempted => exempted.RequirementName == monitoringRequirement.ActionName && (!exempted.DueDate.HasValue || exempted.DueDate == missingDueDate) - && ( - exempted.ExemptionExpiresAt == null - || exempted.ExemptionExpiresAt > today - ) + && (exempted.ExemptionExpiresAt == null || exempted.ExemptionExpiresAt > today) ) ) .Select(missingDueDate => new MissingArrangementRequirement( @@ -528,29 +519,38 @@ ImmutableList completionDates private static IEnumerable> GenerateDateRanges(ImmutableList childLocations) { - DateOnly? startDate = null; - Guid? tag = null; + (DateOnly, Guid)? entry = null; foreach (var childLocation in childLocations) { - if (!startDate.HasValue && !childLocation.Paused) + if (!entry.HasValue && !childLocation.Paused) + { + entry = (childLocation.Date, childLocation.ChildLocationFamilyId); + continue; + } + + if (entry.HasValue && !childLocation.Paused) { - startDate = childLocation.Date; - tag = childLocation.ChildLocationFamilyId; + yield return new DateRange( + entry.Value.Item1, + childLocation.Date.AddDays(-1), + entry.Value.Item2 + ); + entry = (childLocation.Date, childLocation.ChildLocationFamilyId); continue; } - if (startDate.HasValue && tag.HasValue && childLocation.Paused) + if (entry.HasValue && childLocation.Paused) { - yield return new DateRange(startDate.Value, childLocation.Date, tag.Value); - startDate = null; + yield return new DateRange(entry.Value.Item1, childLocation.Date, entry.Value.Item2); + entry = null; continue; } } - if (startDate.HasValue && tag.HasValue) + if (entry.HasValue) { - yield return new DateRange(startDate.Value, tag.Value); + yield return new DateRange(entry.Value.Item1, entry.Value.Item2); } } diff --git a/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs b/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs index dc5c665e..8a37950a 100644 --- a/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs +++ b/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs @@ -16,23 +16,29 @@ public void CreateTimelineWithOneGap() { var result = ReferralCalculations.CreateChildLocationBasedTimeline( H.ChildLocationHistory( - (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), - (Guid.Empty, ChildLocationPlan.WithParent, 1, 12), - (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 20), - (Guid.Empty, ChildLocationPlan.WithParent, 1, 25) - ).ToImmutableList() + (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), + (Guid.Empty, ChildLocationPlan.WithParent, 1, 12), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 20), + (Guid.Empty, ChildLocationPlan.WithParent, 1, 25) + ) + .ToImmutableList() ); - AssertEx.SequenceIs(result, new DateOnlyTimeline([ - new DateRange( - DateOnly.FromDateTime(H.DateTime(1, 1)), - DateOnly.FromDateTime(H.DateTime(1, 12)) - ), - new DateRange( - DateOnly.FromDateTime(H.DateTime(1, 20)), - DateOnly.FromDateTime(H.DateTime(1, 25)) + AssertEx.SequenceIs( + result, + new DateOnlyTimeline( + [ + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 1)), + DateOnly.FromDateTime(H.DateTime(1, 12)) + ), + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 20)), + DateOnly.FromDateTime(H.DateTime(1, 25)) + ), + ] ) - ])); + ); } [TestMethod] @@ -40,29 +46,35 @@ public void CreateTimelineWithTwoGaps() { var result = ReferralCalculations.CreateChildLocationBasedTimeline( H.ChildLocationHistory( - (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), - (Guid.Empty, ChildLocationPlan.WithParent, 1, 12), - (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 20), - (Guid.Empty, ChildLocationPlan.WithParent, 1, 25), - (H.Id('2'), ChildLocationPlan.DaytimeChildCare, 1, 30), - (Guid.Empty, ChildLocationPlan.WithParent, 2, 5) - ).ToImmutableList() + (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), + (Guid.Empty, ChildLocationPlan.WithParent, 1, 12), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 20), + (Guid.Empty, ChildLocationPlan.WithParent, 1, 25), + (H.Id('2'), ChildLocationPlan.DaytimeChildCare, 1, 30), + (Guid.Empty, ChildLocationPlan.WithParent, 2, 5) + ) + .ToImmutableList() ); - AssertEx.SequenceIs(result, new DateOnlyTimeline([ - new DateRange( - DateOnly.FromDateTime(H.DateTime(1, 1)), - DateOnly.FromDateTime(H.DateTime(1, 12)) - ), - new DateRange( - DateOnly.FromDateTime(H.DateTime(1, 20)), - DateOnly.FromDateTime(H.DateTime(1, 25)) - ), - new DateRange( - DateOnly.FromDateTime(H.DateTime(1, 30)), - DateOnly.FromDateTime(H.DateTime(2, 5)) + AssertEx.SequenceIs( + result, + new DateOnlyTimeline( + [ + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 1)), + DateOnly.FromDateTime(H.DateTime(1, 12)) + ), + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 20)), + DateOnly.FromDateTime(H.DateTime(1, 25)) + ), + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 30)), + DateOnly.FromDateTime(H.DateTime(2, 5)) + ), + ] ) - ])); + ); } [TestMethod] @@ -70,26 +82,32 @@ public void CreateTimelineWithTwoGapsFilteredByFamilyId() { var result = ReferralCalculations.CreateChildLocationBasedTimeline( H.ChildLocationHistory( - (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), - (Guid.Empty, ChildLocationPlan.WithParent, 1, 12), - (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 20), - (Guid.Empty, ChildLocationPlan.WithParent, 1, 25), - (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 30), - (Guid.Empty, ChildLocationPlan.WithParent, 2, 5) - ).ToImmutableList(), + (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), + (Guid.Empty, ChildLocationPlan.WithParent, 1, 12), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 20), + (Guid.Empty, ChildLocationPlan.WithParent, 1, 25), + (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 30), + (Guid.Empty, ChildLocationPlan.WithParent, 2, 5) + ) + .ToImmutableList(), filterToFamilyId: H.Id('0') ); - AssertEx.SequenceIs(result, new DateOnlyTimeline([ - new DateRange( - DateOnly.FromDateTime(H.DateTime(1, 1)), - DateOnly.FromDateTime(H.DateTime(1, 12)) - ), - new DateRange( - DateOnly.FromDateTime(H.DateTime(1, 30)), - DateOnly.FromDateTime(H.DateTime(2, 5)) + AssertEx.SequenceIs( + result, + new DateOnlyTimeline( + [ + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 1)), + DateOnly.FromDateTime(H.DateTime(1, 12)) + ), + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 30)), + DateOnly.FromDateTime(H.DateTime(2, 5)) + ), + ] ) - ])); + ); } [TestMethod] @@ -97,21 +115,43 @@ public void CreateTimelineChildWithParentAtEnd() { var result = ReferralCalculations.CreateChildLocationBasedTimeline( H.ChildLocationHistory( - (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), - (Guid.Empty, ChildLocationPlan.WithParent, 1, 12), - (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 20) - ).ToImmutableList() + (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), + (Guid.Empty, ChildLocationPlan.WithParent, 1, 12), + (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 20) + ) + .ToImmutableList() ); - AssertEx.SequenceIs(result, new DateOnlyTimeline([ - new DateRange( - DateOnly.FromDateTime(H.DateTime(1, 1)), - DateOnly.FromDateTime(H.DateTime(1, 12)) - ), - new DateRange( - DateOnly.FromDateTime(H.DateTime(1, 20)) + AssertEx.SequenceIs( + result, + new DateOnlyTimeline( + [ + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 1)), + DateOnly.FromDateTime(H.DateTime(1, 12)) + ), + new DateRange(DateOnly.FromDateTime(H.DateTime(1, 20))), + ] ) - ])); + ); + } + + [TestMethod] + public void CreateTimelineFilteredByFamilyIdNoPauses() + { + var result = ReferralCalculations.CreateChildLocationBasedTimeline( + H.ChildLocationHistory( + (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 20) + ) + .ToImmutableList(), + H.Id('1') + ); + + AssertEx.SequenceIs( + result, + new DateOnlyTimeline([new DateRange(DateOnly.FromDateTime(H.DateTime(1, 20)))]) + ); } } -} \ No newline at end of file +} From da6743f368a687e04068132e8b509bcf695f052d Mon Sep 17 00:00:00 2001 From: PabloDinella Date: Fri, 13 Dec 2024 20:30:18 -0300 Subject: [PATCH 2/5] Handle more complex cases when a child change locations multiple times in the same day --- .../Engines/PolicyEvaluation/ChildLocation.cs | 14 +--- .../PolicyEvaluationEngine.cs | 7 +- .../PolicyEvaluation/ReferralCalculations.cs | 66 +++++++++++++------ .../ReferralEntryForCalculation.cs | 8 +-- .../CreateChildLocationBasedTimeline.cs | 63 ++++++++++++++++++ .../ReferralCalculationTests/Helpers.cs | 4 +- 6 files changed, 120 insertions(+), 42 deletions(-) diff --git a/src/CareTogether.Core/Engines/PolicyEvaluation/ChildLocation.cs b/src/CareTogether.Core/Engines/PolicyEvaluation/ChildLocation.cs index 3f3473e1..031a0603 100644 --- a/src/CareTogether.Core/Engines/PolicyEvaluation/ChildLocation.cs +++ b/src/CareTogether.Core/Engines/PolicyEvaluation/ChildLocation.cs @@ -6,16 +6,4 @@ public sealed record ChildLocation( Guid ChildLocationFamilyId, DateOnly Date, bool Paused // means "from now on, we stop checking for completion until resuming" -) - : IComparable -{ - public int CompareTo(ChildLocation? other) - { - return other == null - ? 1 - : DateTime.Compare( - new DateTime(Date, new TimeOnly()), - new DateTime(other.Date, new TimeOnly()) - ); - } -} +); diff --git a/src/CareTogether.Core/Engines/PolicyEvaluation/PolicyEvaluationEngine.cs b/src/CareTogether.Core/Engines/PolicyEvaluation/PolicyEvaluationEngine.cs index 0a5eb532..d01c7cf7 100644 --- a/src/CareTogether.Core/Engines/PolicyEvaluation/PolicyEvaluationEngine.cs +++ b/src/CareTogether.Core/Engines/PolicyEvaluation/PolicyEvaluationEngine.cs @@ -28,7 +28,10 @@ public async Task CalculateCombinedFamilyApprovalsAsync( ImmutableList completedFamilyRequirements, ImmutableList exemptedFamilyRequirements, ImmutableList familyRoleRemovals, - ImmutableDictionary> completedIndividualRequirements, + ImmutableDictionary< + Guid, + ImmutableList + > completedIndividualRequirements, ImmutableDictionary> exemptedIndividualRequirements, ImmutableDictionary> individualRoleRemovals ) @@ -140,7 +143,7 @@ TimeZoneInfo locationTimeZone var ChildLocationHistory = entry .ChildLocationHistory.Select(item => ToChildLocation(item, locationTimeZone)) - .ToImmutableSortedSet(); + .ToImmutableList(); var individualVolunteerAssignments = entry .IndividualVolunteerAssignments.Select(item => diff --git a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs index 73895df9..43115b7f 100644 --- a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs +++ b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs @@ -393,7 +393,7 @@ internal static ImmutableList CalculateMissingMonitoringRequirementIns DateOnly arrangementStartedAtDate, DateOnly? arrangementEndedAtDate, ImmutableList completions, - ImmutableSortedSet childLocationHistory, + ImmutableList childLocationHistory, DateOnly today ) { @@ -501,7 +501,7 @@ ImmutableList completionDates Guid? filterToFamilyId = null ) { - var dateRanges = GenerateDateRanges(childLocations).ToImmutableList(); + var dateRanges = GenerateDateRanges(childLocations, filterToFamilyId).ToImmutableList(); var filteredDateRanges = ( filterToFamilyId != null ? dateRanges.Where(item => item.Tag == filterToFamilyId) : dateRanges @@ -514,43 +514,71 @@ ImmutableList completionDates return null; } - return new DateOnlyTimeline(filteredDateRanges); + // DateOnlyTimeline.FromOverlappingDateRanges(); + + var nonOverlapping = filteredDateRanges.Aggregate( + ImmutableList.Empty, + (final, item) => + { + var last = final.LastOrDefault(); + + if (last.End >= item.Start) + { + return final.Replace(last, new DateRange(last.Start, item.End)); + } + + return final.Add(item); + } + ); + + return new DateOnlyTimeline(nonOverlapping); } - private static IEnumerable> GenerateDateRanges(ImmutableList childLocations) + private static IEnumerable> GenerateDateRanges( + ImmutableList childLocations, + Guid? filterToFamilyId + ) { - (DateOnly, Guid)? entry = null; + (DateOnly Date, Guid ChildLocationFamilyId)? previousChildLocation = null; foreach (var childLocation in childLocations) { - if (!entry.HasValue && !childLocation.Paused) + if (!previousChildLocation.HasValue && !childLocation.Paused) { - entry = (childLocation.Date, childLocation.ChildLocationFamilyId); + previousChildLocation = (childLocation.Date, childLocation.ChildLocationFamilyId); continue; } - if (entry.HasValue && !childLocation.Paused) + if (previousChildLocation.HasValue && !childLocation.Paused) { yield return new DateRange( - entry.Value.Item1, - childLocation.Date.AddDays(-1), - entry.Value.Item2 + previousChildLocation.Value.Date, + childLocation.Date, + previousChildLocation.Value.ChildLocationFamilyId ); - entry = (childLocation.Date, childLocation.ChildLocationFamilyId); + + previousChildLocation = (childLocation.Date, childLocation.ChildLocationFamilyId); continue; } - if (entry.HasValue && childLocation.Paused) + if (previousChildLocation.HasValue && childLocation.Paused) { - yield return new DateRange(entry.Value.Item1, childLocation.Date, entry.Value.Item2); - entry = null; + yield return new DateRange( + previousChildLocation.Value.Date, + childLocation.Date, + previousChildLocation.Value.ChildLocationFamilyId + ); + previousChildLocation = null; continue; } } - if (entry.HasValue) + if (previousChildLocation.HasValue) { - yield return new DateRange(entry.Value.Item1, entry.Value.Item2); + yield return new DateRange( + previousChildLocation.Value.Date, + previousChildLocation.Value.ChildLocationFamilyId + ); } } @@ -715,7 +743,7 @@ internal static ImmutableList CalculateMissingMonitoringRequirementIns DateOnly? arrangementEndedDate, DateOnly today, ImmutableList completionDates, - ImmutableSortedSet childLocationHistory + ImmutableList childLocationHistory ) { // Technically, the RecurrencePolicyStage model currently allows any stage to have an unlimited @@ -759,7 +787,7 @@ internal static ImmutableList CalculateMissingMonitoringRequirementIns ChildCareOccurrenceBasedRecurrencePolicy recurrence, Guid? filterToFamilyId, ImmutableList completionDates, - ImmutableSortedSet childLocationHistory + ImmutableList childLocationHistory ) { // Determine which child care occurrences the requirement will apply to. diff --git a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralEntryForCalculation.cs b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralEntryForCalculation.cs index 18e6651e..d9507522 100644 --- a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralEntryForCalculation.cs +++ b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralEntryForCalculation.cs @@ -11,11 +11,7 @@ public sealed record ReferralEntry( ImmutableDictionary Arrangements ); - public sealed record CompletedRequirementInfo( - string RequirementName, - DateOnly CompletedAt, - DateOnly? ExpiresAt - ); + public sealed record CompletedRequirementInfo(string RequirementName, DateOnly CompletedAt, DateOnly? ExpiresAt); public sealed record ExemptedRequirementInfo( string RequirementName, @@ -33,7 +29,7 @@ public sealed record ArrangementEntry( ImmutableList ExemptedRequirements, ImmutableList IndividualVolunteerAssignments, ImmutableList FamilyVolunteerAssignments, - ImmutableSortedSet ChildLocationHistory + ImmutableList ChildLocationHistory ); public sealed record IndividualVolunteerAssignment( diff --git a/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs b/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs index 8a37950a..0991041a 100644 --- a/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs +++ b/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs @@ -153,5 +153,68 @@ public void CreateTimelineFilteredByFamilyIdNoPauses() new DateOnlyTimeline([new DateRange(DateOnly.FromDateTime(H.DateTime(1, 20)))]) ); } + + [TestMethod] + public void CreateTimelineFilteredByFamilyIdMultipleChangesInSameDay() + { + var hist = H.ChildLocationHistory( + (H.Id('0'), ChildLocationPlan.WithParent, 1, 1), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('2'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('0'), ChildLocationPlan.WithParent, 1, 15) + ); + + var result = ReferralCalculations.CreateChildLocationBasedTimeline(hist.ToImmutableList(), H.Id('1')); + + AssertEx.SequenceIs( + result, + new DateOnlyTimeline( + [new DateRange(DateOnly.FromDateTime(H.DateTime(1, 10)), DateOnly.FromDateTime(H.DateTime(1, 15)))] + ) + ); + } + + [TestMethod] + public void CreateTimelineFilteredByFamilyIdMultipleChangesInSameDay2() + { + var hist = H.ChildLocationHistory( + (H.Id('0'), ChildLocationPlan.WithParent, 1, 1), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('2'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('0'), ChildLocationPlan.WithParent, 1, 15) + ); + + var result = ReferralCalculations.CreateChildLocationBasedTimeline(hist.ToImmutableList(), H.Id('2')); + + AssertEx.SequenceIs( + result, + new DateOnlyTimeline( + [new DateRange(DateOnly.FromDateTime(H.DateTime(1, 10)), DateOnly.FromDateTime(H.DateTime(1, 10)))] + ) + ); + } + + [TestMethod] + public void CreateTimelineMultipleChangesInSameDay() + { + var hist = H.ChildLocationHistory( + (H.Id('0'), ChildLocationPlan.WithParent, 1, 1), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('2'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('0'), ChildLocationPlan.WithParent, 1, 15) + ); + + var result = ReferralCalculations.CreateChildLocationBasedTimeline(hist.ToImmutableList()); + + AssertEx.SequenceIs( + result, + new DateOnlyTimeline( + [new DateRange(DateOnly.FromDateTime(H.DateTime(1, 10)), DateOnly.FromDateTime(H.DateTime(1, 15)))] + ) + ); + } } } diff --git a/test/CareTogether.Core.Test/ReferralCalculationTests/Helpers.cs b/test/CareTogether.Core.Test/ReferralCalculationTests/Helpers.cs index 006a7366..5fcb077e 100644 --- a/test/CareTogether.Core.Test/ReferralCalculationTests/Helpers.cs +++ b/test/CareTogether.Core.Test/ReferralCalculationTests/Helpers.cs @@ -79,7 +79,7 @@ public static ImmutableSortedSet LocationHistoryEntri )) .ToImmutableSortedSet(); - public static ImmutableSortedSet ChildLocationHistory( + public static ImmutableList ChildLocationHistory( params (Guid childLocationFamilyId, ChildLocationPlan plan, int month, int day)[] values ) => values @@ -88,7 +88,7 @@ public static ImmutableSortedSet ChildLocationHistory( DateOnly.FromDateTime(DateTime(value.month, value.day)), value.plan == ChildLocationPlan.WithParent )) - .ToImmutableSortedSet(); + .ToImmutableList(); public static ArrangementFunction FunctionWithoutEligibility( string arrangementFunction, From 8582c3330e4d3333657703503fac842476eb5551 Mon Sep 17 00:00:00 2001 From: PabloDinella Date: Mon, 16 Dec 2024 11:34:12 -0300 Subject: [PATCH 3/5] Move date ranges merging to DateOnlyTimeline.FromOverlappingDateRanges --- .../PolicyEvaluation/ReferralCalculations.cs | 19 +- src/Timelines/DateOnlyTimeline.cs | 108 ++--- test/Timelines.Test/DateOnlyTimelineTest.cs | 424 +++++++----------- 3 files changed, 222 insertions(+), 329 deletions(-) diff --git a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs index 43115b7f..44cb6fdc 100644 --- a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs +++ b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs @@ -514,24 +514,7 @@ ImmutableList completionDates return null; } - // DateOnlyTimeline.FromOverlappingDateRanges(); - - var nonOverlapping = filteredDateRanges.Aggregate( - ImmutableList.Empty, - (final, item) => - { - var last = final.LastOrDefault(); - - if (last.End >= item.Start) - { - return final.Replace(last, new DateRange(last.Start, item.End)); - } - - return final.Add(item); - } - ); - - return new DateOnlyTimeline(nonOverlapping); + return DateOnlyTimeline.FromOverlappingDateRanges(filteredDateRanges); } private static IEnumerable> GenerateDateRanges( diff --git a/src/Timelines/DateOnlyTimeline.cs b/src/Timelines/DateOnlyTimeline.cs index a5117bbd..2f09ee1c 100644 --- a/src/Timelines/DateOnlyTimeline.cs +++ b/src/Timelines/DateOnlyTimeline.cs @@ -14,7 +14,6 @@ public sealed class DateOnlyTimeline : IEquatable { public ImmutableList Ranges { get; init; } - public DateOnlyTimeline(ImmutableList ranges) { if (ranges.Count == 0) @@ -30,16 +29,35 @@ public DateOnlyTimeline(ImmutableList ranges) var current = Ranges[i]; if (prior.End >= current.Start) - throw new ArgumentException("The date ranges must not overlap. Overlap detected between " + - $"{prior} and {current}."); + throw new ArgumentException( + "The date ranges must not overlap. Overlap detected between " + $"{prior} and {current}." + ); } } - public DateOnly Start => - Ranges.First().Start; + public static DateOnlyTimeline FromOverlappingDateRanges(ImmutableList dateRanges) + { + var mergedDateRanges = dateRanges.Aggregate( + ImmutableList.Empty, + (final, item) => + { + var last = final.LastOrDefault(); + + if (last.End >= item.Start) + { + return final.Replace(last, new DateRange(last.Start, item.End)); + } - public DateOnly End => - Ranges.Last().End; + return final.Add(item); + } + ); + + return new DateOnlyTimeline(mergedDateRanges); + } + + public DateOnly Start => Ranges.First().Start; + + public DateOnly End => Ranges.Last().End; public static DateOnlyTimeline? UnionOf(ImmutableList timelines) { @@ -57,10 +75,7 @@ public DateOnlyTimeline(ImmutableList ranges) if (ranges.Count == 0 || ranges.All(range => range == null)) return null; - var nonNullRanges = ranges - .Where(range => range != null) - .Cast() - .ToImmutableList(); + var nonNullRanges = ranges.Where(range => range != null).Cast().ToImmutableList(); return UnionOf(nonNullRanges); } @@ -70,34 +85,36 @@ public DateOnlyTimeline(ImmutableList ranges) if (ranges.Count == 0) return null; - var orderedAbsoluteRanges = ranges - .OrderBy(range => range.Start) - .ToImmutableList(); + var orderedAbsoluteRanges = ranges.OrderBy(range => range.Start).ToImmutableList(); // Merge any overlapping ranges, i.e., whenever the end of one range is on, // adjacent to, or after the start of the next range, combine them into a single range. var sequentialNonOverlappingRanges = orderedAbsoluteRanges - .Aggregate(new List(), (prior, current) => - { - if (prior.Count == 0) + .Aggregate( + new List(), + (prior, current) => { - prior.Add(current); - return prior; - } + if (prior.Count == 0) + { + prior.Add(current); + return prior; + } + + var mostRecentRange = prior[^1]; + if (mostRecentRange.End == DateOnly.MaxValue || current.Start <= mostRecentRange.End.AddDays(1)) + { + // The resulting range should use the end date of whichever range ends later. + prior[^1] = new DateRange( + mostRecentRange.Start, + mostRecentRange.End > current.End ? mostRecentRange.End : current.End + ); + return prior; + } - var mostRecentRange = prior[^1]; - if (mostRecentRange.End == DateOnly.MaxValue || - current.Start <= mostRecentRange.End.AddDays(1)) - { - // The resulting range should use the end date of whichever range ends later. - prior[^1] = new DateRange(mostRecentRange.Start, - mostRecentRange.End > current.End ? mostRecentRange.End : current.End); + prior.Add(current); return prior; } - - prior.Add(current); - return prior; - }) + ) .ToImmutableList(); return new DateOnlyTimeline(sequentialNonOverlappingRanges); @@ -126,8 +143,8 @@ public DateOnlyTimeline(ImmutableList ranges) return null; var intersection = timelines - .Skip(1).Aggregate(timelines[0], - (prior, current) => prior?.IntersectionWith(current)); + .Skip(1) + .Aggregate(timelines[0], (prior, current) => prior?.IntersectionWith(current)); return intersection; } @@ -141,18 +158,14 @@ public DateOnlyTimeline(ImmutableList ranges) return timeline.Complement(); } - - public bool Contains(DateOnly value) => - Ranges.Exists(range => range.Contains(value)); + public bool Contains(DateOnly value) => Ranges.Exists(range => range.Contains(value)); public DateOnlyTimeline? IntersectionWith(DateRange other) { // Find the intersection of each range in the timeline with the 'other' range, // and then combine the results as a union. Some or all of the intersections // may be null, so the final result may be null. - var rangeIntersections = Ranges - .Select(range => range.IntersectionWith(other)) - .ToImmutableList(); + var rangeIntersections = Ranges.Select(range => range.IntersectionWith(other)).ToImmutableList(); return UnionOf(rangeIntersections); } @@ -167,9 +180,7 @@ public bool Contains(DateOnly value) => // any such intersection must consist at most of all the time spans in the first timeline. // So, we can find the intersection by finding the intersection of each stage in the first // timeline with the second timeline, then combining the results as a union. - var intersections = other.Ranges - .Select(otherRange => IntersectionWith(otherRange)) - .ToImmutableList(); + var intersections = other.Ranges.Select(otherRange => IntersectionWith(otherRange)).ToImmutableList(); return UnionOf(intersections); } @@ -243,9 +254,7 @@ public bool Contains(DateOnly value) => // Otherwise, the forward-only complement is the regular complement, but // with the first range removed. - var rangesAfterFirst = fullComplement.Ranges - .Skip(1) - .ToImmutableList(); + var rangesAfterFirst = fullComplement.Ranges.Skip(1).ToImmutableList(); return UnionOf(rangesAfterFirst); } @@ -259,7 +268,6 @@ public bool Contains(DateOnly value) => return IntersectionWith(ComplementOf(other)); } - public bool Equals(DateOnlyTimeline? other) { return other != null && Ranges.SequenceEqual(other.Ranges); @@ -332,7 +340,6 @@ public sealed class DateOnlyTimeline : IEquatable> { public ImmutableList> Ranges { get; init; } - public DateOnlyTimeline(ImmutableList> ranges) { if (ranges.Count == 0) @@ -348,12 +355,12 @@ public DateOnlyTimeline(ImmutableList> ranges) var current = Ranges[i]; if (prior.End >= current.Start) - throw new ArgumentException("The date ranges must not overlap. Overlap detected between " + - $"{prior} and {current}."); + throw new ArgumentException( + "The date ranges must not overlap. Overlap detected between " + $"{prior} and {current}." + ); } } - public T? ValueAt(DateOnly value) => // Because DateRange is a struct, the 'default' value is a range that has 'Tag' set to default(T). // That will be null for reference types, and the default value for value types. @@ -361,7 +368,6 @@ public DateOnlyTimeline(ImmutableList> ranges) public T? ValueAt(DateTime value) => ValueAt(DateOnly.FromDateTime(value)); - public bool Equals(DateOnlyTimeline? other) { return other != null && Ranges.SequenceEqual(other.Ranges); diff --git a/test/Timelines.Test/DateOnlyTimelineTest.cs b/test/Timelines.Test/DateOnlyTimelineTest.cs index 044e7c93..a74a5737 100644 --- a/test/Timelines.Test/DateOnlyTimelineTest.cs +++ b/test/Timelines.Test/DateOnlyTimelineTest.cs @@ -6,7 +6,9 @@ namespace Timelines.Test; public class DateOnlyTimelineTest { private static DateOnly D(int day) => new(2024, 1, day); + private static DateRange DR(int start, int end) => new(D(start), D(end)); + private static DateRange DR(int start, int end, T tag) => new(D(start), D(end), tag); private static void AssertDatesAre(DateOnlyTimeline dut, params int[] dates) @@ -16,26 +18,22 @@ private static void AssertDatesAre(DateOnlyTimeline dut, params int[] dates) Assert.AreEqual(dates.Contains(i), dut.Contains(D(i)), $"Failed on {i}"); } - [TestMethod] public void ConstructorForbidsEmptyList() { - Assert.ThrowsException(() => - new DateOnlyTimeline(ImmutableList.Empty)); + Assert.ThrowsException(() => new DateOnlyTimeline(ImmutableList.Empty)); } [TestMethod] public void ConstructorForbidsOverlappingRanges() { - Assert.ThrowsException(() => - new DateOnlyTimeline([DR(1, 2), DR(2, 3)])); + Assert.ThrowsException(() => new DateOnlyTimeline([DR(1, 2), DR(2, 3)])); } [TestMethod] public void ConstructorForbidsOverlappingRanges2() { - Assert.ThrowsException(() => - new DateOnlyTimeline([DR(1, 3), DR(2, 4)])); + Assert.ThrowsException(() => new DateOnlyTimeline([DR(1, 3), DR(2, 4)])); } [TestMethod] @@ -44,9 +42,23 @@ public void ConstructorPopulatesRanges() var dut = new DateOnlyTimeline([DR(1, 2), DR(3, 4)]); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 2), DR(3, 4) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 2), DR(3, 4)])); + } + + [TestMethod] + public void CreateTimelineFromOverlappingDateRanges() + { + var dut = DateOnlyTimeline.FromOverlappingDateRanges([DR(1, 2), DR(2, 4)]); + + Assert.AreEqual(dut, new DateOnlyTimeline([DR(1, 4)])); + } + + [TestMethod] + public void CreateTimelineFromOverlappingDateRangesWithNonOverlappingDateRanges() + { + var dut = DateOnlyTimeline.FromOverlappingDateRanges([DR(1, 2), DR(4, 5)]); + + Assert.AreEqual(dut, new DateOnlyTimeline([DR(1, 2), DR(4, 5)])); } [TestMethod] @@ -96,9 +108,7 @@ public void TakeDaysReturnsOriginalTimelineWhenRequestedLengthExceedsTotal() var result = dut.TakeDays(10); Assert.IsNotNull(result); - Assert.IsTrue(result.Ranges.SequenceEqual([ - DR(1, 3), DR(5, 6) - ])); + Assert.IsTrue(result.Ranges.SequenceEqual([DR(1, 3), DR(5, 6)])); } [TestMethod] @@ -108,9 +118,7 @@ public void TakeDaysReturnsPartialTimelineWhenRequestedLengthIsLess() var result = dut.TakeDays(4); Assert.IsNotNull(result); - Assert.IsTrue(result.Ranges.SequenceEqual([ - DR(1, 3), DR(5, 5) - ])); + Assert.IsTrue(result.Ranges.SequenceEqual([DR(1, 3), DR(5, 5)])); } [TestMethod] @@ -120,9 +128,7 @@ public void TakeDaysHandlesSingleRange() var result = dut.TakeDays(3); Assert.IsNotNull(result); - Assert.IsTrue(result.Ranges.SequenceEqual([ - DR(1, 3) - ])); + Assert.IsTrue(result.Ranges.SequenceEqual([DR(1, 3)])); } [TestMethod] @@ -136,105 +142,81 @@ public void UnionOfEmptyListReturnsNull() [TestMethod] public void UnionOfSingleOneDayStage() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create( - DR(1, 1))); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(1, 1))); Assert.IsNotNull(dut); AssertDatesAre(dut, 1); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 1) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 1)])); } [TestMethod] public void UnionOfMultipleOverlappingOneDayStages() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create( - DR(1, 1), DR(1, 1))); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(1, 1), DR(1, 1))); Assert.IsNotNull(dut); AssertDatesAre(dut, 1); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 1) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 1)])); } [TestMethod] public void UnionOfTwoContiguousOneDayStages() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create( - DR(1, 1), DR(2, 2))); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(1, 1), DR(2, 2))); Assert.IsNotNull(dut); AssertDatesAre(dut, 1, 2); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 2) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 2)])); } [TestMethod] public void UnionOfOneMultipleDayRange() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create( - DR(2, 4))); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(2, 4))); Assert.IsNotNull(dut); AssertDatesAre(dut, 2, 3, 4); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(2, 4) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(2, 4)])); } [TestMethod] public void UnionOfTwoDiscontinuousMultipleDayRanges() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create( - DR(2, 3), DR(5, 5), DR(6, 8))); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(2, 3), DR(5, 5), DR(6, 8))); Assert.IsNotNull(dut); AssertDatesAre(dut, 2, 3, 5, 6, 7, 8); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(2, 3), DR(5, 8) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(2, 3), DR(5, 8)])); } [TestMethod] public void UnionOfTwoOverlappingMultipleDayRanges() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create( - DR(2, 5), DR(4, 7))); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(2, 5), DR(4, 7))); Assert.IsNotNull(dut); AssertDatesAre(dut, 2, 3, 4, 5, 6, 7); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(2, 7) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(2, 7)])); } [TestMethod] public void UnionOfTwoOverlappingMultipleDayRangesInReverseOrder() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create( - DR(4, 7), DR(2, 5))); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(4, 7), DR(2, 5))); Assert.IsNotNull(dut); AssertDatesAre(dut, 2, 3, 4, 5, 6, 7); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(2, 7) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(2, 7)])); } [TestMethod] public void UnionOfTwoFullyOverlappingMultipleDayRanges() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create( - DR(2, 7), DR(4, 5))); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(2, 7), DR(4, 5))); Assert.IsNotNull(dut); AssertDatesAre(dut, 2, 3, 4, 5, 6, 7); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(2, 7) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(2, 7)])); } [TestMethod] @@ -248,8 +230,7 @@ public void UnionOfNullableEmptyListReturnsNull() [TestMethod] public void UnionOfNullableNullElementReturnsNull() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty - .Add(null)); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty.Add(null)); Assert.IsNull(dut); } @@ -257,8 +238,7 @@ public void UnionOfNullableNullElementReturnsNull() [TestMethod] public void UnionOfNullableNullElementsReturnsNull() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty - .AddRange([null, null])); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty.AddRange([null, null])); Assert.IsNull(dut); } @@ -266,60 +246,50 @@ public void UnionOfNullableNullElementsReturnsNull() [TestMethod] public void UnionOfNullableSingleOneDayStage() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty - .Add(DR(1, 1))); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty.Add(DR(1, 1))); Assert.IsNotNull(dut); AssertDatesAre(dut, 1); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 1) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 1)])); } [TestMethod] public void UnionOfNullableSingleOneDayStageWithNulls() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty - .AddRange([null, DR(1, 1), null])); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty.AddRange([null, DR(1, 1), null])); Assert.IsNotNull(dut); AssertDatesAre(dut, 1); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 1) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 1)])); } [TestMethod] public void UnionOfNullableTwoDiscontinuousMultipleDayRangesWithNulls() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty - .AddRange([DR(2, 3), null, null, DR(5, 5), null, null, DR(6, 8), null])); + var dut = DateOnlyTimeline.UnionOf( + ImmutableList.Empty.AddRange([DR(2, 3), null, null, DR(5, 5), null, null, DR(6, 8), null]) + ); Assert.IsNotNull(dut); AssertDatesAre(dut, 2, 3, 5, 6, 7, 8); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(2, 3), DR(5, 8) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(2, 3), DR(5, 8)])); } [TestMethod] public void UnionOfTwoUnendingRangesReturnsSingleOverlappingRange() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Create( - new DateRange(D(2), DateOnly.MaxValue), - new DateRange(D(4), DateOnly.MaxValue))); + var dut = DateOnlyTimeline.UnionOf( + ImmutableList.Create(new DateRange(D(2), DateOnly.MaxValue), new DateRange(D(4), DateOnly.MaxValue)) + ); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(D(2), DateOnly.MaxValue) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([new DateRange(D(2), DateOnly.MaxValue)])); } [TestMethod] public void UnionOfTimelinesReturnsNullIfAllNull() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty - .AddRange([null, null])); + var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty.AddRange([null, null])); Assert.IsNull(dut); } @@ -327,21 +297,22 @@ public void UnionOfTimelinesReturnsNullIfAllNull() [TestMethod] public void UnionOfTimelinesReturnsUnionOfAllNonNullRanges() { - var dut = DateOnlyTimeline.UnionOf(ImmutableList.Empty - .AddRange([ - null, - DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(1, 1))), - null, - DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(2, 2))), - DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(4, 5))), - null - ])); + var dut = DateOnlyTimeline.UnionOf( + ImmutableList.Empty.AddRange( + [ + null, + DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(1, 1))), + null, + DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(2, 2))), + DateOnlyTimeline.UnionOf(ImmutableList.Create(DR(4, 5))), + null, + ] + ) + ); Assert.IsNotNull(dut); AssertDatesAre(dut, 1, 2, 4, 5); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 2), DR(4, 5) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 2), DR(4, 5)])); } [TestMethod] @@ -355,40 +326,35 @@ public void IntersectionOfEmptyListReturnsNull() [TestMethod] public void IntersectionOfSingleOneDayTimeline() { - var dut = DateOnlyTimeline.IntersectionOf(ImmutableList.Create([ - new DateOnlyTimeline([DR(1, 1)]) - ])); + var dut = DateOnlyTimeline.IntersectionOf( + ImmutableList.Create([new DateOnlyTimeline([DR(1, 1)])]) + ); Assert.IsNotNull(dut); AssertDatesAre(dut, 1); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 1) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 1)])); } [TestMethod] public void IntersectionOfTwoOverlappingTimelines() { - var dut = DateOnlyTimeline.IntersectionOf(ImmutableList.Create([ - new DateOnlyTimeline([DR(2, 2)]), - new DateOnlyTimeline([DR(1, 3)]) - ])); + var dut = DateOnlyTimeline.IntersectionOf( + ImmutableList.Create( + [new DateOnlyTimeline([DR(2, 2)]), new DateOnlyTimeline([DR(1, 3)])] + ) + ); Assert.IsNotNull(dut); AssertDatesAre(dut, 2); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(2, 2) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(2, 2)])); } [TestMethod] public void IntersectionOfTwoOverlappingTimelinesAndNull() { - var dut = DateOnlyTimeline.IntersectionOf(ImmutableList.Create([ - new DateOnlyTimeline([DR(2, 2)]), - new DateOnlyTimeline([DR(1, 3)]), - null - ])); + var dut = DateOnlyTimeline.IntersectionOf( + ImmutableList.Create([new DateOnlyTimeline([DR(2, 2)]), new DateOnlyTimeline([DR(1, 3)]), null]) + ); Assert.IsNull(dut); } @@ -396,11 +362,11 @@ public void IntersectionOfTwoOverlappingTimelinesAndNull() [TestMethod] public void IntersectionOfThreeDisjointTimelines() { - var dut = DateOnlyTimeline.IntersectionOf(ImmutableList.Create([ - new DateOnlyTimeline([DR(2, 2)]), - new DateOnlyTimeline([DR(1, 3)]), - new DateOnlyTimeline([DR(4, 4)]) - ])); + var dut = DateOnlyTimeline.IntersectionOf( + ImmutableList.Create( + [new DateOnlyTimeline([DR(2, 2)]), new DateOnlyTimeline([DR(1, 3)]), new DateOnlyTimeline([DR(4, 4)])] + ) + ); Assert.IsNull(dut); } @@ -408,17 +374,15 @@ public void IntersectionOfThreeDisjointTimelines() [TestMethod] public void IntersectionOfThreeOverlappingTimelines() { - var dut = DateOnlyTimeline.IntersectionOf(ImmutableList.Create([ - new DateOnlyTimeline([DR(2, 2)]), - new DateOnlyTimeline([DR(1, 3)]), - new DateOnlyTimeline([DR(2, 4)]) - ])); + var dut = DateOnlyTimeline.IntersectionOf( + ImmutableList.Create( + [new DateOnlyTimeline([DR(2, 2)]), new DateOnlyTimeline([DR(1, 3)]), new DateOnlyTimeline([DR(2, 4)])] + ) + ); Assert.IsNotNull(dut); AssertDatesAre(dut, 2); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(2, 2) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(2, 2)])); } [TestMethod] @@ -460,9 +424,7 @@ public void IntersectionWithOverlappingTimelineReturnsIntersection() Assert.IsNotNull(intersection); AssertDatesAre(intersection, 3, 4); - Assert.IsTrue(intersection.Ranges.SequenceEqual([ - DR(3, 4) - ])); + Assert.IsTrue(intersection.Ranges.SequenceEqual([DR(3, 4)])); } public void IntersectionWithDisjointDateRangeReturnsNull() @@ -483,9 +445,7 @@ public void IntersectionWithOverlappingDateRangeReturnsIntersection() Assert.IsNotNull(intersection); AssertDatesAre(intersection, 3); - Assert.IsTrue(intersection.Ranges.SequenceEqual([ - DR(3, 3) - ])); + Assert.IsTrue(intersection.Ranges.SequenceEqual([DR(3, 3)])); } [TestMethod] @@ -494,17 +454,13 @@ public void ComplementOfNullIsAllOfTime() var dut = DateOnlyTimeline.ComplementOf(null); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(DateOnly.MinValue, DateOnly.MaxValue) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([new DateRange(DateOnly.MinValue, DateOnly.MaxValue)])); } [TestMethod] public void ComplementOfAllOfTimeIsNull() { - var input = new DateOnlyTimeline([ - new DateRange(DateOnly.MinValue, DateOnly.MaxValue) - ]); + var input = new DateOnlyTimeline([new DateRange(DateOnly.MinValue, DateOnly.MaxValue)]); var dut = DateOnlyTimeline.ComplementOf(input); Assert.IsNull(dut); @@ -513,9 +469,7 @@ public void ComplementOfAllOfTimeIsNull() [TestMethod] public void ComplementOfAllOfTimeIsNullViaInstanceMethod() { - var input = new DateOnlyTimeline([ - new DateRange(DateOnly.MinValue, DateOnly.MaxValue) - ]); + var input = new DateOnlyTimeline([new DateRange(DateOnly.MinValue, DateOnly.MaxValue)]); var dut = input.Complement(); Assert.IsNull(dut); @@ -524,114 +478,97 @@ public void ComplementOfAllOfTimeIsNullViaInstanceMethod() [TestMethod] public void ComplementOfSingleRangeIsTwoRanges() { - var input = new DateOnlyTimeline([ - DR(1, 3) - ]); + var input = new DateOnlyTimeline([DR(1, 3)]); var dut = input.Complement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(DateOnly.MinValue, D(1).AddDays(-1)), - new DateRange(D(3).AddDays(1), DateOnly.MaxValue) - ])); + Assert.IsTrue( + dut.Ranges.SequenceEqual( + [new DateRange(DateOnly.MinValue, D(1).AddDays(-1)), new DateRange(D(3).AddDays(1), DateOnly.MaxValue)] + ) + ); } [TestMethod] public void ComplementOfTwoRangesIsThreeRanges() { - var input = new DateOnlyTimeline([ - DR(1, 3), DR(5, 5) - ]); + var input = new DateOnlyTimeline([DR(1, 3), DR(5, 5)]); var dut = input.Complement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(DateOnly.MinValue, D(1).AddDays(-1)), - new DateRange(D(3).AddDays(1), D(5).AddDays(-1)), - new DateRange(D(5).AddDays(1), DateOnly.MaxValue) - ])); + Assert.IsTrue( + dut.Ranges.SequenceEqual( + [ + new DateRange(DateOnly.MinValue, D(1).AddDays(-1)), + new DateRange(D(3).AddDays(1), D(5).AddDays(-1)), + new DateRange(D(5).AddDays(1), DateOnly.MaxValue), + ] + ) + ); } [TestMethod] public void ComplementOfTwoAdjacentRangesIsTwoRanges() { - var input = new DateOnlyTimeline([ - DR(1, 3), DR(4, 5) - ]); + var input = new DateOnlyTimeline([DR(1, 3), DR(4, 5)]); var dut = input.Complement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(DateOnly.MinValue, D(1).AddDays(-1)), - new DateRange(D(5).AddDays(1), DateOnly.MaxValue) - ])); + Assert.IsTrue( + dut.Ranges.SequenceEqual( + [new DateRange(DateOnly.MinValue, D(1).AddDays(-1)), new DateRange(D(5).AddDays(1), DateOnly.MaxValue)] + ) + ); } [TestMethod] public void ComplementOfRangeBeforeBeginningOfTimeDoesNotExist() { - var input = new DateOnlyTimeline([ - new DateRange(DateOnly.MinValue, D(3)) - ]); + var input = new DateOnlyTimeline([new DateRange(DateOnly.MinValue, D(3))]); var dut = input.Complement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(D(3).AddDays(1), DateOnly.MaxValue) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([new DateRange(D(3).AddDays(1), DateOnly.MaxValue)])); } [TestMethod] public void ComplementOfRangeAfterEndOfTimeDoesNotExist() { - var input = new DateOnlyTimeline([ - new DateRange(D(1), DateOnly.MaxValue) - ]); + var input = new DateOnlyTimeline([new DateRange(D(1), DateOnly.MaxValue)]); var dut = input.Complement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(DateOnly.MinValue, D(1).AddDays(-1)) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([new DateRange(DateOnly.MinValue, D(1).AddDays(-1))])); } [TestMethod] public void ComplementOfSecondRangeAfterEndOfTimeDoesNotExist() { - var input = new DateOnlyTimeline([ - new DateRange(D(1), D(3)), - new DateRange(D(4), DateOnly.MaxValue) - ]); + var input = new DateOnlyTimeline([new DateRange(D(1), D(3)), new DateRange(D(4), DateOnly.MaxValue)]); var dut = input.Complement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(DateOnly.MinValue, D(1).AddDays(-1)) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([new DateRange(DateOnly.MinValue, D(1).AddDays(-1))])); } [TestMethod] public void ComplementOfSecondRangeAfterEndOfTimeDoesNotExist2() { - var input = new DateOnlyTimeline([ - new DateRange(D(1), D(2)), - new DateRange(D(4), DateOnly.MaxValue) - ]); + var input = new DateOnlyTimeline([new DateRange(D(1), D(2)), new DateRange(D(4), DateOnly.MaxValue)]); var dut = input.Complement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(DateOnly.MinValue, D(1).AddDays(-1)), - new DateRange(D(2).AddDays(1), D(4).AddDays(-1)) - ])); + Assert.IsTrue( + dut.Ranges.SequenceEqual( + [new DateRange(DateOnly.MinValue, D(1).AddDays(-1)), new DateRange(D(2).AddDays(1), D(4).AddDays(-1))] + ) + ); } [TestMethod] public void ForwardOnlyComplementOfAllOfTimeIsNull() { - var input = new DateOnlyTimeline([ - new DateRange(DateOnly.MinValue, DateOnly.MaxValue) - ]); + var input = new DateOnlyTimeline([new DateRange(DateOnly.MinValue, DateOnly.MaxValue)]); var dut = input.ForwardOnlyComplement(); Assert.IsNull(dut); @@ -640,68 +577,55 @@ public void ForwardOnlyComplementOfAllOfTimeIsNull() [TestMethod] public void ForwardOnlyComplementOfSingleRangeIsOneRange() { - var input = new DateOnlyTimeline([ - DR(1, 3) - ]); + var input = new DateOnlyTimeline([DR(1, 3)]); var dut = input.ForwardOnlyComplement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(D(3).AddDays(1), DateOnly.MaxValue) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([new DateRange(D(3).AddDays(1), DateOnly.MaxValue)])); } [TestMethod] public void ForwardOnlyComplementOfTwoRangesIsTwoRanges() { - var input = new DateOnlyTimeline([ - DR(1, 3), DR(5, 5) - ]); + var input = new DateOnlyTimeline([DR(1, 3), DR(5, 5)]); var dut = input.ForwardOnlyComplement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(D(3).AddDays(1), D(5).AddDays(-1)), - new DateRange(D(5).AddDays(1), DateOnly.MaxValue) - ])); + Assert.IsTrue( + dut.Ranges.SequenceEqual( + [new DateRange(D(3).AddDays(1), D(5).AddDays(-1)), new DateRange(D(5).AddDays(1), DateOnly.MaxValue)] + ) + ); } [TestMethod] public void ForwardOnlyComplementOfTwoAdjacentRangesIsOneRange() { - var input = new DateOnlyTimeline([ - DR(1, 3), DR(4, 5) - ]); + var input = new DateOnlyTimeline([DR(1, 3), DR(4, 5)]); var dut = input.ForwardOnlyComplement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(D(5).AddDays(1), DateOnly.MaxValue) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([new DateRange(D(5).AddDays(1), DateOnly.MaxValue)])); } [TestMethod] public void ForwardOnlyComplementOfRangeBeforeBeginningOfTimeDoesNotExist() { - var input = new DateOnlyTimeline([ - new DateRange(DateOnly.MinValue, D(3)), - new DateRange(D(5), D(7)) - ]); + var input = new DateOnlyTimeline([new DateRange(DateOnly.MinValue, D(3)), new DateRange(D(5), D(7))]); var dut = input.ForwardOnlyComplement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(D(3).AddDays(1), D(5).AddDays(-1)), - new DateRange(D(7).AddDays(1), DateOnly.MaxValue) - ])); + Assert.IsTrue( + dut.Ranges.SequenceEqual( + [new DateRange(D(3).AddDays(1), D(5).AddDays(-1)), new DateRange(D(7).AddDays(1), DateOnly.MaxValue)] + ) + ); } [TestMethod] public void ForwardOnlyComplementOfRangeAfterEndOfTimeDoesNotExist() { - var input = new DateOnlyTimeline([ - new DateRange(D(1), DateOnly.MaxValue) - ]); + var input = new DateOnlyTimeline([new DateRange(D(1), DateOnly.MaxValue)]); var dut = input.ForwardOnlyComplement(); Assert.IsNull(dut); @@ -710,16 +634,11 @@ public void ForwardOnlyComplementOfRangeAfterEndOfTimeDoesNotExist() [TestMethod] public void ForwardOnlyComplementOfSecondRangeAfterEndOfTimeDoesNotExist() { - var input = new DateOnlyTimeline([ - new DateRange(D(1), D(2)), - new DateRange(D(4), DateOnly.MaxValue) - ]); + var input = new DateOnlyTimeline([new DateRange(D(1), D(2)), new DateRange(D(4), DateOnly.MaxValue)]); var dut = input.ForwardOnlyComplement(); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - new DateRange(D(2).AddDays(1), D(4).AddDays(-1)) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([new DateRange(D(2).AddDays(1), D(4).AddDays(-1))])); } [TestMethod] @@ -729,18 +648,14 @@ public void DifferenceWithNullReturnsTheOriginal() var dut = input.Difference(null); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 1), DR(3, 4) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 1), DR(3, 4)])); } [TestMethod] public void DifferenceWithAllOfTimeReturnsNull() { var input = new DateOnlyTimeline([DR(1, 1), DR(3, 4)]); - var dut = input.Difference(new DateOnlyTimeline([ - new DateRange(DateOnly.MinValue, DateOnly.MaxValue) - ])); + var dut = input.Difference(new DateOnlyTimeline([new DateRange(DateOnly.MinValue, DateOnly.MaxValue)])); Assert.IsNull(dut); } @@ -752,9 +667,7 @@ public void DifferenceWithDisjointRangesReturnsTheOriginal() var dut = input.Difference(new DateOnlyTimeline([DR(5, 7)])); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 1), DR(3, 4) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 1), DR(3, 4)])); } [TestMethod] @@ -764,9 +677,7 @@ public void DifferenceWithPartialOverlapExcludesTheOverlappedDates() var dut = input.Difference(new DateOnlyTimeline([DR(4, 7)])); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 1), DR(3, 3) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 1), DR(3, 3)])); } [TestMethod] @@ -776,9 +687,7 @@ public void DifferenceWithPartialOverlapExcludesTheOverlappedDates2() var dut = input.Difference(new DateOnlyTimeline([DR(2, 7)])); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 1) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 1)])); } [TestMethod] @@ -788,9 +697,7 @@ public void DifferenceWithPartialOverlapExcludesTheOverlappedDates3() var dut = input.Difference(new DateOnlyTimeline([DR(1, 3)])); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(4, 4) - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(4, 4)])); } [TestMethod] @@ -866,22 +773,21 @@ public void GetHashCodeIsConsistent3() [TestMethod] public void TaggedConstructorForbidsEmptyList() { - Assert.ThrowsException(() => - new DateOnlyTimeline(ImmutableList>.Empty)); + Assert.ThrowsException( + () => new DateOnlyTimeline(ImmutableList>.Empty) + ); } [TestMethod] public void TaggedConstructorForbidsOverlappingRanges() { - Assert.ThrowsException(() => - new DateOnlyTimeline([DR(1, 2, 'A'), DR(2, 3, 'A')])); + Assert.ThrowsException(() => new DateOnlyTimeline([DR(1, 2, 'A'), DR(2, 3, 'A')])); } [TestMethod] public void TaggedConstructorForbidsOverlappingRanges2() { - Assert.ThrowsException(() => - new DateOnlyTimeline([DR(1, 3, 'A'), DR(2, 4, 'A')])); + Assert.ThrowsException(() => new DateOnlyTimeline([DR(1, 3, 'A'), DR(2, 4, 'A')])); } [TestMethod] @@ -890,9 +796,7 @@ public void TaggedConstructorPopulatesRanges() var dut = new DateOnlyTimeline([DR(1, 2, 'A'), DR(3, 4, 'B')]); Assert.IsNotNull(dut); - Assert.IsTrue(dut.Ranges.SequenceEqual([ - DR(1, 2, 'A'), DR(3, 4, 'B') - ])); + Assert.IsTrue(dut.Ranges.SequenceEqual([DR(1, 2, 'A'), DR(3, 4, 'B')])); } [DataRow(1, default(char))] From abdf075b3223f2fc300fbf9b98ba065700f3739a Mon Sep 17 00:00:00 2001 From: PabloDinella Date: Thu, 9 Jan 2025 19:00:22 -0300 Subject: [PATCH 4/5] Added CreateTimelineFilteredByFamilyIdWithPauses unit test --- .../CreateChildLocationBasedTimeline.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs b/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs index 0991041a..ea5baea6 100644 --- a/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs +++ b/test/CareTogether.Core.Test/ReferralCalculationTests/CreateChildLocationBasedTimeline.cs @@ -154,6 +154,38 @@ public void CreateTimelineFilteredByFamilyIdNoPauses() ); } + [TestMethod] + public void CreateTimelineFilteredByFamilyIdWithPauses() + { + var result = ReferralCalculations.CreateChildLocationBasedTimeline( + H.ChildLocationHistory( + (H.Id('0'), ChildLocationPlan.DaytimeChildCare, 1, 1), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 10), + (H.Id('2'), ChildLocationPlan.WithParent, 1, 12), + (H.Id('1'), ChildLocationPlan.DaytimeChildCare, 1, 15), + (H.Id('2'), ChildLocationPlan.WithParent, 1, 20) + ) + .ToImmutableList(), + H.Id('1') + ); + + AssertEx.SequenceIs( + result, + new DateOnlyTimeline( + [ + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 10)), + DateOnly.FromDateTime(H.DateTime(1, 12)) + ), + new DateRange( + DateOnly.FromDateTime(H.DateTime(1, 15)), + DateOnly.FromDateTime(H.DateTime(1, 20)) + ), + ] + ) + ); + } + [TestMethod] public void CreateTimelineFilteredByFamilyIdMultipleChangesInSameDay() { From bc02425f6f1c992f77ccb1d68d0429a75184b043 Mon Sep 17 00:00:00 2001 From: PabloDinella Date: Fri, 10 Jan 2025 10:43:13 -0300 Subject: [PATCH 5/5] Remove unused parameter --- .../Engines/PolicyEvaluation/ReferralCalculations.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs index 44cb6fdc..ddf4c4ca 100644 --- a/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs +++ b/src/CareTogether.Core/Engines/PolicyEvaluation/ReferralCalculations.cs @@ -501,7 +501,7 @@ ImmutableList completionDates Guid? filterToFamilyId = null ) { - var dateRanges = GenerateDateRanges(childLocations, filterToFamilyId).ToImmutableList(); + var dateRanges = GenerateDateRanges(childLocations).ToImmutableList(); var filteredDateRanges = ( filterToFamilyId != null ? dateRanges.Where(item => item.Tag == filterToFamilyId) : dateRanges @@ -517,10 +517,7 @@ ImmutableList completionDates return DateOnlyTimeline.FromOverlappingDateRanges(filteredDateRanges); } - private static IEnumerable> GenerateDateRanges( - ImmutableList childLocations, - Guid? filterToFamilyId - ) + private static IEnumerable> GenerateDateRanges(ImmutableList childLocations) { (DateOnly Date, Guid ChildLocationFamilyId)? previousChildLocation = null;