diff --git a/DigitalLearningSolutions.Data.Tests/DataServices/CompetencyLearningResourcesDataServiceTests.cs b/DigitalLearningSolutions.Data.Tests/DataServices/CompetencyLearningResourcesDataServiceTests.cs index cab3b825a6..7604bfd4ff 100644 --- a/DigitalLearningSolutions.Data.Tests/DataServices/CompetencyLearningResourcesDataServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/DataServices/CompetencyLearningResourcesDataServiceTests.cs @@ -78,7 +78,7 @@ public void GetCompetencyResourceAssessmentQuestionParameters_returns_expected_r testHelper.InsertLearningResourceReference(2, 2, adminId, "Resource 2"); testHelper.InsertCompetencyLearningResource(1, 1, 2, adminId); - testHelper.InsertCompetencyResourceAssessmentQuestionParameters(1, 1, true, 2, false); + testHelper.InsertCompetencyResourceAssessmentQuestionParameters(1, 1, true, 2, false, 1, 10); var expectedItem = new CompetencyResourceAssessmentQuestionParameter { CompetencyLearningResourceId = 1, @@ -86,6 +86,8 @@ public void GetCompetencyResourceAssessmentQuestionParameters_returns_expected_r Essential = true, RelevanceAssessmentQuestionId = 2, CompareToRoleRequirements = false, + MinResultMatch = 1, + MaxResultMatch = 10, }; // When diff --git a/DigitalLearningSolutions.Data.Tests/DigitalLearningSolutions.Data.Tests.csproj b/DigitalLearningSolutions.Data.Tests/DigitalLearningSolutions.Data.Tests.csproj index 426bc90d5a..1e96c40bbc 100644 --- a/DigitalLearningSolutions.Data.Tests/DigitalLearningSolutions.Data.Tests.csproj +++ b/DigitalLearningSolutions.Data.Tests/DigitalLearningSolutions.Data.Tests.csproj @@ -42,6 +42,7 @@ + diff --git a/DigitalLearningSolutions.Data.Tests/Extensions/EnumerableExtensionsTests.cs b/DigitalLearningSolutions.Data.Tests/Extensions/EnumerableExtensionsTests.cs new file mode 100644 index 0000000000..e5b7ad9c65 --- /dev/null +++ b/DigitalLearningSolutions.Data.Tests/Extensions/EnumerableExtensionsTests.cs @@ -0,0 +1,23 @@ +namespace DigitalLearningSolutions.Data.Tests.Extensions +{ + using System.Collections.Generic; + using DigitalLearningSolutions.Data.Extensions; + using FluentAssertions; + using NUnit.Framework; + + public class EnumerableExtensionsTests + { + [Test] + public void WhereNotNull_filters_items_correctly() + { + // Given + var list = new List { "1", "2", null, "3", null, "4" }; + + // When + var result = list.WhereNotNull(); + + // Then + result.Should().BeEquivalentTo(new List { "1", "2", "3", "4" }); + } + } +} diff --git a/DigitalLearningSolutions.Data.Tests/Services/RecommendedLearningServiceTests.cs b/DigitalLearningSolutions.Data.Tests/Services/RecommendedLearningServiceTests.cs index fa4c83746f..6788d0f3dc 100644 --- a/DigitalLearningSolutions.Data.Tests/Services/RecommendedLearningServiceTests.cs +++ b/DigitalLearningSolutions.Data.Tests/Services/RecommendedLearningServiceTests.cs @@ -20,11 +20,13 @@ public class RecommendedLearningServiceTests { private const int SelfAssessmentId = 1; private const int CompetencyId = 2; + private const int SecondCompetencyId = 3; private const int LearningResourceReferenceId = 3; private const int LearningHubResourceReferenceId = 4; private const int DelegateId = 5; private const int LearningLogId = 6; private const int CompetencyLearningResourceId = 1; + private const int SecondCompetencyLearningResourceId = 2; private const int CompetencyAssessmentQuestionId = 1; private const int RelevanceAssessmentQuestionId = 2; private const string ResourceName = "Resource"; @@ -61,10 +63,13 @@ public async Task { // Given GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); + GivenQuestionParametersAreReturned(true, true, 1, 10); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); + A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(DelegateId)) .Returns(new List()); - var expectedResource = GetExpectedResource(false, false, null); + var expectedResource = GetExpectedResource(false, false, null, 175); // When var result = @@ -82,6 +87,8 @@ public async Task { // Given GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); + GivenQuestionParametersAreReturned(true, true, 1, 10); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); var learningLogItems = Builder.CreateListOfSize(5).All() .With(i => i.LearningHubResourceReferenceId = LearningHubResourceReferenceId + 1) @@ -89,7 +96,7 @@ public async Task A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(DelegateId)) .Returns(learningLogItems); - var expectedResource = GetExpectedResource(false, false, null); + var expectedResource = GetExpectedResource(false, false, null, 175); // When var result = @@ -107,6 +114,8 @@ public async Task { // Given GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); + GivenQuestionParametersAreReturned(true, true, 1, 10); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); var learningLogItem = Builder.CreateNew() .With(i => i.LearningHubResourceReferenceId = LearningHubResourceReferenceId) @@ -116,7 +125,7 @@ public async Task A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(DelegateId)) .Returns(new List { learningLogItem }); - var expectedResource = GetExpectedResource(false, true, null); + var expectedResource = GetExpectedResource(false, true, null, 175); // When var result = @@ -134,6 +143,8 @@ public async Task { // Given GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); + GivenQuestionParametersAreReturned(true, true, 1, 10); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); var learningLogItem = Builder.CreateNew() .With(i => i.LearningHubResourceReferenceId = LearningHubResourceReferenceId) @@ -143,7 +154,7 @@ public async Task A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(DelegateId)) .Returns(new List { learningLogItem }); - var expectedResource = GetExpectedResource(true, false, LearningLogId); + var expectedResource = GetExpectedResource(true, false, LearningLogId, 175); // When var result = @@ -161,21 +172,23 @@ public async Task { // Given GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); + GivenQuestionParametersAreReturned(true, true, 1, 10); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); - var completeLearningLogItem = Builder.CreateNew() + var learningLogItems = Builder.CreateListOfSize(2) + .All() .With(i => i.LearningHubResourceReferenceId = LearningHubResourceReferenceId) - .And(i => i.CompletedDate = DateTime.UtcNow) .And(i => i.LearningLogItemId = LearningLogId) - .And(i => i.ArchivedDate = null).Build(); - var incompleteLearningLogItem = Builder.CreateNew() - .With(i => i.LearningHubResourceReferenceId = LearningHubResourceReferenceId) - .And(i => i.CompletedDate = null) - .And(i => i.LearningLogItemId = LearningLogId) - .And(i => i.ArchivedDate = null).Build(); + .And(i => i.ArchivedDate = null) + .TheFirst(1) + .With(i => i.CompletedDate = DateTime.UtcNow) + .TheRest() + .With(i => i.CompletedDate = null) + .Build(); A.CallTo(() => learningLogItemsDataService.GetLearningLogItems(DelegateId)) - .Returns(new List { completeLearningLogItem, incompleteLearningLogItem }); + .Returns(learningLogItems); - var expectedResource = GetExpectedResource(true, true, LearningLogId); + var expectedResource = GetExpectedResource(true, true, LearningLogId, 175); // When var result = @@ -201,26 +214,7 @@ public async Task GetRecommendedLearningForSelfAssessment_calls_learning_hub_api () => competencyLearningResourcesDataService.GetCompetencyLearningResourcesByCompetencyId(A._) ).Returns(competencyLearningResources); - var clientResponse = new BulkResourceReferences - { - ResourceReferences = new List - { - new ResourceReferenceWithResourceDetails - { - ResourceId = 0, - RefId = LearningHubResourceReferenceId, - Title = ResourceName, - Description = ResourceDescription, - Catalogue = new Catalogue { Name = ResourceCatalogue }, - ResourceType = ResourceType, - Rating = 0, - Link = ResourceLink, - }, - }, - }; - - A.CallTo(() => learningHubApiClient.GetBulkResourcesByReferenceIds(A>._)) - .Returns(clientResponse); + GivenLearningHubApiReturnsResources(0); // When await recommendedLearningService.GetRecommendedLearningForSelfAssessment(SelfAssessmentId, DelegateId); @@ -235,12 +229,10 @@ public async Task GetRecommendedLearningForSelfAssessment_calls_learning_hub_api } [Test] - [TestCase(1, true, 100)] - [TestCase(1, false, 30)] - [TestCase(0, true, 0)] + [TestCase(true, 175)] + [TestCase(false, 105)] public async Task - GetRecommendedLearningForSelfAssessment_returns_correct_recommendation_score_for_resource_with_essential_question_parameters_only( - int numberOfQuestionParameters, + GetRecommendedLearningForSelfAssessment_returns_correct_recommendation_score_for_resource_with_essential_question_parameters( bool essential, decimal expectedScore ) @@ -248,19 +240,8 @@ decimal expectedScore // Given GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); GivenGetLearningLogItemsReturnsAnItem(); - - var questionParameters = - numberOfQuestionParameters == 0 - ? new List() - : Builder - .CreateListOfSize(numberOfQuestionParameters).All() - .With(qp => qp.Essential = essential).Build(); - - A.CallTo( - () => competencyLearningResourcesDataService.GetCompetencyResourceAssessmentQuestionParameters( - A>._ - ) - ).Returns(questionParameters); + GivenQuestionParametersAreReturned(essential, true, 1, 10); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); var expectedResource = GetExpectedResource(true, false, LearningLogId, expectedScore); @@ -276,17 +257,26 @@ decimal expectedScore [Test] public async Task - GetRecommendedLearningForSelfAssessment_returns_correct_recommendation_score_for_resource_with__multiple_essential_question_parameters_of_different_value() + GetRecommendedLearningForSelfAssessment_returns_correct_recommendation_score_for_resource_with_multiple_competencies_with_essential_question_parameters_of_different_value() { // Given - GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); + GivenResourceHasTwoCompetencies(); + GivenLearningHubApiReturnsResources(0); GivenGetLearningLogItemsReturnsAnItem(); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); var questionParameters = Builder - .CreateListOfSize(5).TheFirst(2) - .With(qp => qp.Essential = true) - .TheRest() - .With(qp => qp.Essential = false).Build(); + .CreateListOfSize(2) + .All() + .With(qp => qp.MinResultMatch = 1) + .And(qp => qp.MaxResultMatch = 10) + .TheFirst(1) + .With(qp => qp.Essential = true) + .And(qp => qp.CompetencyLearningResourceId = CompetencyLearningResourceId) + .TheRest() + .With(qp => qp.Essential = false) + .And(qp => qp.CompetencyLearningResourceId = 2) + .Build(); A.CallTo( () => competencyLearningResourcesDataService.GetCompetencyResourceAssessmentQuestionParameters( @@ -294,7 +284,7 @@ public async Task ) ).Returns(questionParameters); - var expectedResource = GetExpectedResource(true, false, LearningLogId, 100); + var expectedResource = GetExpectedResource(true, false, LearningLogId, 175); // When var result = @@ -312,7 +302,7 @@ public async Task [TestCase(2.4, 9.6)] [TestCase(5, 20)] public async Task - GetRecommendedLearningForSelfAssessment_returns_correct_recommendation_score_for_resource_with_learning_hub_ratings_only( + GetRecommendedLearningForSelfAssessment_returns_correct_recommendation_score_for_resource_with_learning_hub_ratings_and_no_question_parameters( decimal learningHubRating, decimal expectedScore ) @@ -320,6 +310,7 @@ decimal expectedScore // Given GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(learningHubRating); GivenGetLearningLogItemsReturnsAnItem(); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); var expectedResource = GetExpectedResource(true, false, LearningLogId, expectedScore); @@ -347,16 +338,8 @@ decimal expectedScore // Given GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); GivenGetLearningLogItemsReturnsAnItem(); - - var questionParameters = Builder - .CreateListOfSize(1).All() - .With(qp => qp.Essential = true) - .And(qp => qp.CompareToRoleRequirements = true).Build(); - A.CallTo( - () => competencyLearningResourcesDataService.GetCompetencyResourceAssessmentQuestionParameters( - A>._ - ) - ).Returns(questionParameters); + GivenQuestionParametersAreReturned(true, true, 1, 10); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); var roleRequirement = Builder.CreateNew() .With(rr => rr.LevelRag = levelRag).Build(); @@ -400,26 +383,7 @@ decimal expectedScore GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); GivenGetLearningLogItemsReturnsAnItem(); GivenNotComparingToRoleRequirements(); - - var assessmentResults = Builder.CreateListOfSize(2) - .All() - .With(r => r.SelfAssessmentId = SelfAssessmentId) - .And(r => r.CandidateId = DelegateId) - .And(r => r.CompetencyId = CompetencyId) - .TheFirst(1) - .With(r => r.AssessmentQuestionId = CompetencyAssessmentQuestionId) - .And(r => r.Result = confidenceResult) - .TheRest() - .With(r => r.AssessmentQuestionId = RelevanceAssessmentQuestionId) - .And(r => r.Result = relevanceResult) - .Build(); - A.CallTo( - () => selfAssessmentDataService.GetSelfAssessmentResultsForDelegateSelfAssessmentCompetency( - DelegateId, - SelfAssessmentId, - CompetencyId - ) - ).Returns(assessmentResults); + GivenSelfAssessmentHasResultsForFirstCompetency(relevanceResult, confidenceResult); var expectedResource = GetExpectedResource(true, false, LearningLogId, expectedScore); @@ -441,7 +405,7 @@ decimal expectedScore [Test] public async Task - GetRecommendedLearningForSelfAssessment_returns_correct_recommendation_score_for_resource_with_missing_self_assessment_results() + GetRecommendedLearningForSelfAssessment_does_not_return_resources_relating_to_unanswered_optional_competencies() { // Given GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); @@ -456,16 +420,13 @@ public async Task ) ).Returns(new List()); - var expectedResource = GetExpectedResource(true, false, LearningLogId, 100); - // When var result = (await recommendedLearningService.GetRecommendedLearningForSelfAssessment(SelfAssessmentId, DelegateId)) .ToList(); // Then - result.Should().HaveCount(1); - result.Single().Should().BeEquivalentTo(expectedResource); + result.Should().BeEmpty(); A.CallTo( () => selfAssessmentDataService.GetCompetencyAssessmentQuestionRoleRequirements( A._, @@ -474,7 +435,83 @@ public async Task ).MustNotHaveHappened(); } + [Test] + public async Task + GetRecommendedLearningForSelfAssessment_does_return_resources_with_some_unanswered_optional_competencies() + { + // Given + GivenResourceHasTwoCompetencies(); + GivenLearningHubApiReturnsResources(0); + GivenGetLearningLogItemsReturnsAnItem(); + GivenSelfAssessmentHasResultsForFirstCompetency(5, 5); + + var questionParameters = Builder + .CreateListOfSize(2) + .All() + .With(qp => qp.MinResultMatch = 1) + .And(qp => qp.MaxResultMatch = 10) + .TheFirst(1) + .With(qp => qp.Essential = true) + .And(qp => qp.CompetencyLearningResourceId = CompetencyLearningResourceId) + .TheRest() + .With(qp => qp.Essential = false) + .And(qp => qp.CompetencyLearningResourceId = SecondCompetencyLearningResourceId) + .Build(); + + A.CallTo( + () => competencyLearningResourcesDataService.GetCompetencyResourceAssessmentQuestionParameters( + A>._ + ) + ).Returns(questionParameters); + + var expectedResource = GetExpectedResource(true, false, LearningLogId, 175); + + // When + var result = + (await recommendedLearningService.GetRecommendedLearningForSelfAssessment(SelfAssessmentId, DelegateId)) + .ToList(); + + // Then + result.Should().HaveCount(1); + result.Single().Should().BeEquivalentTo(expectedResource); + } + + [Test] + [TestCase(1, 10, 5, 1)] + [TestCase(5, 10, 4, 0)] + [TestCase(1, 5, 6, 0)] + [TestCase(3, 7, 3, 1)] + [TestCase(3, 7, 7, 1)] + public async Task + GetRecommendedLearningForSelfAssessment_returns_expected_number_of_resources_for_answers_in_and_out_of_range( + int minScore, + int maxScore, + int confidenceResult, + int expectedResultCount + ) + { + // Given + GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(); + GivenGetLearningLogItemsReturnsAnItem(); + GivenQuestionParametersAreReturned(true, true, minScore, maxScore); + GivenSelfAssessmentHasResultsForFirstCompetency(5, confidenceResult); + + // When + var result = + (await recommendedLearningService.GetRecommendedLearningForSelfAssessment(SelfAssessmentId, DelegateId)) + .ToList(); + + // Then + result.Should().HaveCount(expectedResultCount); + } + private void GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(decimal rating = 0) + { + GivenSingleCompetencyExistsForResource(); + GivenLearningHubApiReturnsResources(rating); + } + + private void GivenSingleCompetencyExistsForResource() { A.CallTo(() => selfAssessmentDataService.GetCompetencyIdsForSelfAssessment(SelfAssessmentId)) .Returns(new[] { CompetencyId }); @@ -491,7 +528,33 @@ private void GivenResourceForSelfAssessmentIsReturnedByLearningHubApi(decimal ra A.CallTo( () => competencyLearningResourcesDataService.GetCompetencyLearningResourcesByCompetencyId(CompetencyId) ).Returns(new List { competencyLearningResource }); + } + + private void GivenResourceHasTwoCompetencies() + { + A.CallTo(() => selfAssessmentDataService.GetCompetencyIdsForSelfAssessment(SelfAssessmentId)) + .Returns(new[] { CompetencyId, SecondCompetencyId }); + + var competencyLearningResources = Builder.CreateListOfSize(2) + .All() + .With(clr => clr.LearningResourceReferenceId = LearningResourceReferenceId) + .And(clr => clr.LearningHubResourceReferenceId = LearningHubResourceReferenceId) + .And(clr => clr.AdminId = 7) + .TheFirst(1) + .With(clr => clr.Id = CompetencyLearningResourceId) + .And(clr => clr.CompetencyId = CompetencyId) + .TheRest() + .With(clr => clr.Id = SecondCompetencyLearningResourceId) + .And(clr => clr.CompetencyId = SecondCompetencyId) + .Build(); + A.CallTo( + () => competencyLearningResourcesDataService.GetCompetencyLearningResourcesByCompetencyId(CompetencyId) + ).Returns(competencyLearningResources); + } + + private void GivenLearningHubApiReturnsResources(decimal rating) + { var clientResponse = new BulkResourceReferences { ResourceReferences = new List @@ -531,8 +594,34 @@ private void GivenNotComparingToRoleRequirements() .CreateListOfSize(1).All() .With(qp => qp.Essential = true) .And(qp => qp.CompareToRoleRequirements = false) + .And(qp => qp.CompetencyLearningResourceId = CompetencyLearningResourceId) .And(qp => qp.AssessmentQuestionId = CompetencyAssessmentQuestionId) - .And(qp => qp.RelevanceAssessmentQuestionId = RelevanceAssessmentQuestionId).Build(); + .And(qp => qp.RelevanceAssessmentQuestionId = RelevanceAssessmentQuestionId) + .And(qp => qp.MinResultMatch = 1) + .And(qp => qp.MaxResultMatch = 10) + .Build(); + A.CallTo( + () => competencyLearningResourcesDataService.GetCompetencyResourceAssessmentQuestionParameters( + A>._ + ) + ).Returns(questionParameters); + } + + private void GivenQuestionParametersAreReturned( + bool essential, + bool compareToRoleRequirements, + int minMatch, + int maxMatch + ) + { + var questionParameters = Builder + .CreateListOfSize(1).All() + .With(qp => qp.Essential = essential) + .And(qp => qp.CompareToRoleRequirements = compareToRoleRequirements) + .And(qp => qp.MinResultMatch = minMatch) + .And(qp => qp.MaxResultMatch = maxMatch) + .And(qp => qp.CompetencyLearningResourceId = CompetencyLearningResourceId) + .Build(); A.CallTo( () => competencyLearningResourcesDataService.GetCompetencyResourceAssessmentQuestionParameters( A>._ @@ -540,6 +629,29 @@ private void GivenNotComparingToRoleRequirements() ).Returns(questionParameters); } + private void GivenSelfAssessmentHasResultsForFirstCompetency(int relevanceResult, int confidenceResult) + { + var assessmentResults = Builder.CreateListOfSize(2) + .All() + .With(r => r.SelfAssessmentId = SelfAssessmentId) + .And(r => r.CandidateId = DelegateId) + .And(r => r.CompetencyId = CompetencyId) + .TheFirst(1) + .With(r => r.AssessmentQuestionId = CompetencyAssessmentQuestionId) + .And(r => r.Result = confidenceResult) + .TheRest() + .With(r => r.AssessmentQuestionId = RelevanceAssessmentQuestionId) + .And(r => r.Result = relevanceResult) + .Build(); + A.CallTo( + () => selfAssessmentDataService.GetSelfAssessmentResultsForDelegateSelfAssessmentCompetency( + DelegateId, + SelfAssessmentId, + CompetencyId + ) + ).Returns(assessmentResults); + } + private RecommendedResource GetExpectedResource( bool isInActionPlan, bool isCompleted, diff --git a/DigitalLearningSolutions.Data.Tests/TestHelpers/CompetencyLearningResourcesTestHelper.cs b/DigitalLearningSolutions.Data.Tests/TestHelpers/CompetencyLearningResourcesTestHelper.cs index 94280a115a..b71410cb21 100644 --- a/DigitalLearningSolutions.Data.Tests/TestHelpers/CompetencyLearningResourcesTestHelper.cs +++ b/DigitalLearningSolutions.Data.Tests/TestHelpers/CompetencyLearningResourcesTestHelper.cs @@ -47,14 +47,20 @@ public void InsertCompetencyResourceAssessmentQuestionParameters( int assessmentQuestionId, bool essential, int? relevanceQuestionId, - bool compareToRoleRequirements + bool compareToRoleRequirements, + int minMatchResult, + int maxMatchResult ) { connection.Execute( @"INSERT INTO CompetencyResourceAssessmentQuestionParameters (CompetencyLearningResourceId, AssessmentQuestionID, Essential, RelevanceAssessmentQuestionID, CompareToRoleRequirements, MinResultMatch, MaxResultMatch) - VALUES (@competencyLearningResourceId, @assessmentQuestionId, @essential, @relevanceQuestionId, @compareToRoleRequirements, 0, 0)", - new { competencyLearningResourceId, assessmentQuestionId, essential, relevanceQuestionId, compareToRoleRequirements } + VALUES (@competencyLearningResourceId, @assessmentQuestionId, @essential, @relevanceQuestionId, @compareToRoleRequirements, @minMatchResult, @maxMatchResult)", + new + { + competencyLearningResourceId, assessmentQuestionId, essential, relevanceQuestionId, + compareToRoleRequirements, minMatchResult, maxMatchResult, + } ); } } diff --git a/DigitalLearningSolutions.Data/DataServices/CompetencyLearningResourcesDataService.cs b/DigitalLearningSolutions.Data/DataServices/CompetencyLearningResourcesDataService.cs index e594c73092..b280331c58 100644 --- a/DigitalLearningSolutions.Data/DataServices/CompetencyLearningResourcesDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/CompetencyLearningResourcesDataService.cs @@ -96,7 +96,9 @@ public IEnumerable AssessmentQuestionID, Essential, RelevanceAssessmentQuestionID, - CompareToRoleRequirements + CompareToRoleRequirements, + MinResultMatch, + MaxResultMatch FROM CompetencyResourceAssessmentQuestionParameters WHERE CompetencyLearningResourceId IN @competencyLearningResourceIds", new { competencyLearningResourceIds } diff --git a/DigitalLearningSolutions.Data/Extensions/EnumerableExtensions.cs b/DigitalLearningSolutions.Data/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000000..00ab26d727 --- /dev/null +++ b/DigitalLearningSolutions.Data/Extensions/EnumerableExtensions.cs @@ -0,0 +1,13 @@ +namespace DigitalLearningSolutions.Data.Extensions +{ + using System.Collections.Generic; + using System.Linq; + + public static class EnumerableExtensions + { + public static IEnumerable WhereNotNull(this IEnumerable enumerable) where T : class + { + return enumerable.Where(e => e != null).Select(e => e!); + } + } +} diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/CompetencyResourceAssessmentQuestionParameter.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/CompetencyResourceAssessmentQuestionParameter.cs index 915c6fb33a..c0dc1ae4f7 100644 --- a/DigitalLearningSolutions.Data/Models/SelfAssessments/CompetencyResourceAssessmentQuestionParameter.cs +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/CompetencyResourceAssessmentQuestionParameter.cs @@ -11,5 +11,9 @@ public class CompetencyResourceAssessmentQuestionParameter public int? RelevanceAssessmentQuestionId { get; set; } public bool CompareToRoleRequirements { get; set; } + + public int MinResultMatch { get; set; } + + public int MaxResultMatch { get; set; } } } diff --git a/DigitalLearningSolutions.Data/Services/RecommendedLearningService.cs b/DigitalLearningSolutions.Data/Services/RecommendedLearningService.cs index 17aebe527f..2294aced17 100644 --- a/DigitalLearningSolutions.Data/Services/RecommendedLearningService.cs +++ b/DigitalLearningSolutions.Data/Services/RecommendedLearningService.cs @@ -6,6 +6,7 @@ using DigitalLearningSolutions.Data.ApiClients; using DigitalLearningSolutions.Data.DataServices; using DigitalLearningSolutions.Data.DataServices.SelfAssessmentDataService; + using DigitalLearningSolutions.Data.Extensions; using DigitalLearningSolutions.Data.Models.External.LearningHubApiClient; using DigitalLearningSolutions.Data.Models.LearningResources; using DigitalLearningSolutions.Data.Models.SelfAssessments; @@ -67,40 +68,119 @@ int delegateId var delegateLearningLogItems = learningLogItemsDataService.GetLearningLogItems(delegateId); var recommendedResources = resources.ResourceReferences.Select( - rr => - { - var learningLogItemsForResource = delegateLearningLogItems.Where( - ll => ll.ArchivedDate == null && ll.LearningHubResourceReferenceId == rr.RefId - ).ToList(); - var incompleteLearningLogItem = - learningLogItemsForResource.SingleOrDefault(ll => ll.CompletedDate == null); - return new RecommendedResource( - resourceReferences[rr.RefId], - rr, - incompleteLearningLogItem, - learningLogItemsForResource.Any(ll => ll.CompletedDate != null), - CalculateRecommendedLearningScore(rr, competencyLearningResources, selfAssessmentId, delegateId) - ); - } + rr => GetPopulatedRecommendedResource( + selfAssessmentId, + delegateId, + resourceReferences[rr.RefId], + delegateLearningLogItems, + rr, + competencyLearningResources + ) ); - return recommendedResources; + return recommendedResources.WhereNotNull(); } - private decimal CalculateRecommendedLearningScore( - ResourceReferenceWithResourceDetails resource, - List competencyLearningResources, + private RecommendedResource? GetPopulatedRecommendedResource( int selfAssessmentId, - int delegateId + int delegateId, + int learningHubResourceReferenceId, + IEnumerable delegateLearningLogItems, + ResourceReferenceWithResourceDetails rr, + List competencyLearningResources ) { + var learningLogItemsForResource = delegateLearningLogItems.Where( + ll => ll.ArchivedDate == null && ll.LearningHubResourceReferenceId == rr.RefId + ).ToList(); + var incompleteLearningLogItem = + learningLogItemsForResource.SingleOrDefault(ll => ll.CompletedDate == null); + var clrsForResource = - competencyLearningResources.Where(clr => clr.LearningHubResourceReferenceId == resource.RefId).ToList(); + competencyLearningResources.Where(clr => clr.LearningHubResourceReferenceId == rr.RefId) + .ToList(); var competencyResourceAssessmentQuestionParameters = competencyLearningResourcesDataService - .GetCompetencyResourceAssessmentQuestionParameters(clrsForResource.Select(clr => clr.Id)).ToList(); + .GetCompetencyResourceAssessmentQuestionParameters(clrsForResource.Select(clr => clr.Id)) + .ToList(); + if (!AreDelegateAnswersWithinRangeToDisplayResource( + clrsForResource, + competencyResourceAssessmentQuestionParameters, + selfAssessmentId, + delegateId + )) + { + return null; + } + + return new RecommendedResource( + learningHubResourceReferenceId, + rr, + incompleteLearningLogItem, + learningLogItemsForResource.Any(ll => ll.CompletedDate != null), + CalculateRecommendedLearningScore( + rr, + clrsForResource, + competencyResourceAssessmentQuestionParameters, + selfAssessmentId, + delegateId + ) + ); + } + + private bool AreDelegateAnswersWithinRangeToDisplayResource( + List clrsForResource, + List competencyResourceAssessmentQuestionParameters, + int selfAssessmentId, + int delegateId + ) + { + foreach (var competencyLearningResource in clrsForResource) + { + var delegateResults = selfAssessmentDataService + .GetSelfAssessmentResultsForDelegateSelfAssessmentCompetency( + delegateId, + selfAssessmentId, + competencyLearningResource.CompetencyId + ).ToList(); + + var competencyResourceAssessmentQuestionParametersForClr = + competencyResourceAssessmentQuestionParameters.SingleOrDefault( + qp => qp.CompetencyLearningResourceId == competencyLearningResource.Id + ); + + if (competencyResourceAssessmentQuestionParametersForClr == null) + { + return true; + } + + var latestConfidenceResult = delegateResults + .Where( + dr => dr.AssessmentQuestionId == + competencyResourceAssessmentQuestionParametersForClr.AssessmentQuestionId + ) + .OrderByDescending(dr => dr.DateTime).FirstOrDefault(); + + if (competencyResourceAssessmentQuestionParametersForClr.MinResultMatch <= latestConfidenceResult?.Result && + latestConfidenceResult.Result <= competencyResourceAssessmentQuestionParametersForClr.MaxResultMatch) + { + return true; + } + } + + return false; + } + + private decimal CalculateRecommendedLearningScore( + ResourceReferenceWithResourceDetails resource, + List clrsForResource, + List competencyResourceAssessmentQuestionParameters, + int selfAssessmentId, + int delegateId + ) + { var essentialnessValue = CalculateEssentialnessValue(competencyResourceAssessmentQuestionParameters); var learningHubRating = resource.Rating; @@ -134,17 +214,17 @@ int delegateId foreach (var competencyLearningResource in competencyLearningResources) { - var competencyResourceAssessmentQuestionParameterForClr = + var competencyResourceAssessmentQuestionParametersForClr = competencyResourceAssessmentQuestionParameters.SingleOrDefault( c => c.CompetencyLearningResourceId == competencyLearningResource.Id ); - if (competencyResourceAssessmentQuestionParameterForClr == null) + if (competencyResourceAssessmentQuestionParametersForClr == null) { break; } - if (competencyResourceAssessmentQuestionParameterForClr.CompareToRoleRequirements) + if (competencyResourceAssessmentQuestionParametersForClr.CompareToRoleRequirements) { requirementAdjusters.Add( CalculateRoleRequirementValue(competencyLearningResource.CompetencyId, selfAssessmentId) @@ -158,7 +238,7 @@ int delegateId .CompetencyId, selfAssessmentId, delegateId, - competencyResourceAssessmentQuestionParameterForClr + competencyResourceAssessmentQuestionParametersForClr ) ); }