From 4603e9295e81ee8955d8de81368dbba880a5a69b Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Tue, 19 Aug 2025 08:12:56 +0100 Subject: [PATCH 1/3] TD-5339 Self Assessment Process Agreement --- .../CandidateAssessmentsDataService.cs | 19 ++++- .../SelfAssessmentDataService.cs | 1 + .../SelfAssessments/CurrentSelfAssessment.cs | 1 + .../LearningPortal/SelfAssessmentTests.cs | 81 +++++++++++++++++++ .../SelfAssessment.cs | 36 +++++++++ .../Services/SelfAssessmentService.cs | 6 ++ .../SelfAssessmentDescriptionViewModel.cs | 2 + .../SelfAssessmentProcessViewModel.cs | 14 ++++ .../AgreeSelfAssessmentProcess.cshtml | 55 +++++++++++++ 9 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentProcessViewModel.cs create mode 100644 DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/AgreeSelfAssessmentProcess.cshtml diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs index bb1dc7649f..7009b70e53 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/CandidateAssessmentsDataService.cs @@ -193,6 +193,7 @@ CandidateAssessments AS CA LEFT OUTER JOIN SA.LinearNavigation, SA.UseDescriptionExpanders, SA.ManageOptionalCompetenciesPrompt, + CAST(CASE WHEN CA.SelfAssessmentProcessAgreed IS NOT NULL THEN 1 ELSE 0 END AS BIT) AS SelfAssessmentProcessAgreed, CAST(CASE WHEN SA.SupervisorSelfAssessmentReview = 1 OR SA.SupervisorResultsReview = 1 THEN 1 ELSE 0 END AS BIT) AS IsSupervised, CASE WHEN (SELECT COUNT(*) FROM SelfAssessmentSupervisorRoles WHERE SelfAssessmentID = @selfAssessmentId AND AllowDelegateNomination = 1) > 0 @@ -241,7 +242,7 @@ GROUP BY CA.LaunchCount, CA.SubmittedDate, SA.LinearNavigation, SA.UseDescriptionExpanders, SA.ManageOptionalCompetenciesPrompt, SA.SupervisorSelfAssessmentReview, SA.SupervisorResultsReview, SA.ReviewerCommentsLabel,SA.EnforceRoleRequirementsForSignOff, SA.ManageSupervisorsDescription,CA.NonReportable, - U.FirstName , U.LastName,SA.MinimumOptionalCompetencies", + U.FirstName , U.LastName,SA.MinimumOptionalCompetencies, CA.SelfAssessmentProcessAgreed", new { delegateUserId, selfAssessmentId } ); } @@ -325,6 +326,22 @@ public void SetCompleteByDate(int selfAssessmentId, int delegateUserId, DateTime } } + public void MarkProgressAgreed(int selfAssessmentId, int delegateUserId) + { + var numberOfAffectedRows = connection.Execute( + @"UPDATE CandidateAssessments SET SelfAssessmentProcessAgreed = GETDATE() + WHERE SelfAssessmentID = @selfAssessmentId AND DelegateUserID = @delegateUserId", + new { selfAssessmentId, delegateUserId } + ); + if (numberOfAffectedRows < 1) + { + logger.LogWarning( + "SelfAssessmentProcessAgreed not set as db update failed. " + + $"Self assessment id: {selfAssessmentId}, Delegate User id: {delegateUserId}" + ); + } + } + public void SetUpdatedFlag(int selfAssessmentId, int delegateUserId, bool status) { var numberOfAffectedRows = connection.Execute( diff --git a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs index 324a1050a7..dd5dc5233c 100644 --- a/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs +++ b/DigitalLearningSolutions.Data/DataServices/SelfAssessmentDataService/SelfAssessmentDataService.cs @@ -89,6 +89,7 @@ int competencyId void SetBookmark(int selfAssessmentId, int delegateUserId, string bookmark); + void MarkProgressAgreed(int selfAssessmentId, int delegateUserId); IEnumerable GetCandidateAssessments(int delegateUserId, int selfAssessmentId); // SelfAssessmentSupervisorDataService diff --git a/DigitalLearningSolutions.Data/Models/SelfAssessments/CurrentSelfAssessment.cs b/DigitalLearningSolutions.Data/Models/SelfAssessments/CurrentSelfAssessment.cs index ccd5af82f5..408c06a647 100644 --- a/DigitalLearningSolutions.Data/Models/SelfAssessments/CurrentSelfAssessment.cs +++ b/DigitalLearningSolutions.Data/Models/SelfAssessments/CurrentSelfAssessment.cs @@ -24,5 +24,6 @@ public class CurrentSelfAssessment : SelfAssessment public int? DelegateUserId { get; set; } public string? DelegateName { get; set; } public string? EnrolledByFullName { get; set; } + public bool SelfAssessmentProcessAgreed { get; set; } } } diff --git a/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/SelfAssessmentTests.cs b/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/SelfAssessmentTests.cs index 977c4445e3..e725347d28 100644 --- a/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/SelfAssessmentTests.cs +++ b/DigitalLearningSolutions.Web.Tests/Controllers/LearningPortal/SelfAssessmentTests.cs @@ -870,5 +870,86 @@ public void SelfAssessmentOverview_Should_Return_View_With_Optional_Filter_Appli result.Should().BeViewResult().ModelAs().CompetencyGroups.ToList()[0].Count().Should().Be(1); } + + [Test] + public void SelfAssessment_should_return_process_agreement_view_when_not_agreed_and_supervised() + { + // Given + var selfAssessment = SelfAssessmentTestHelper.CreateDefaultSelfAssessment(); + selfAssessment.IsSupervised = true; + selfAssessment.SelfAssessmentProcessAgreed = false; + A.CallTo(() => selfAssessmentService.GetSelfAssessmentForCandidateById(DelegateUserId, SelfAssessmentId)) + .Returns(selfAssessment); + A.CallTo(() => selfAssessmentService.GetAllSupervisorsForSelfAssessmentId(SelfAssessmentId, DelegateUserId)) + .Returns(new List()); + + // When + var result = controller.SelfAssessment(SelfAssessmentId); + + // Then + result.Should().BeViewResult() + .WithViewName("SelfAssessments/AgreeSelfAssessmentProcess") + .Model.Should().BeOfType() + .Which.SelfAssessmentID.Should().Be(SelfAssessmentId); + } + + [Test] + public void SelfAssessment_should_return_description_view_when_process_agreed_or_not_supervised() + { + // Given + var selfAssessment = SelfAssessmentTestHelper.CreateDefaultSelfAssessment(); + selfAssessment.IsSupervised = false; // or set SelfAssessmentProcessAgreed = true + A.CallTo(() => selfAssessmentService.GetSelfAssessmentForCandidateById(DelegateUserId, SelfAssessmentId)) + .Returns(selfAssessment); + A.CallTo(() => selfAssessmentService.GetAllSupervisorsForSelfAssessmentId(SelfAssessmentId, DelegateUserId)) + .Returns(new List()); + var expectedModel = new SelfAssessmentDescriptionViewModel(selfAssessment, new List()); + + // When + var result = controller.SelfAssessment(SelfAssessmentId); + + // Then + result.Should().BeViewResult() + .WithViewName("SelfAssessments/SelfAssessmentDescription") + .Model.Should().BeEquivalentTo(expectedModel); + } + + [Test] + public void ProcessAgreed_should_return_agree_view_when_modelstate_invalid() + { + // Given + var model = new SelfAssessmentProcessViewModel { SelfAssessmentID = SelfAssessmentId }; + controller.ModelState.AddModelError("Test", "Error"); + + // When + var result = controller.ProcessAgreed(model); + + // Then + result.Should().BeViewResult() + .WithViewName("SelfAssessments/AgreeSelfAssessmentProcess") + .Model.Should().Be(model); + } + + [Test] + public void ProcessAgreed_should_mark_progress_and_return_description_view() + { + // Given + var selfAssessment = SelfAssessmentTestHelper.CreateDefaultSelfAssessment(); + var supervisors = new List(); + var model = new SelfAssessmentProcessViewModel { SelfAssessmentID = SelfAssessmentId }; + A.CallTo(() => selfAssessmentService.GetSelfAssessmentForCandidateById(DelegateUserId, SelfAssessmentId)) + .Returns(selfAssessment); + A.CallTo(() => selfAssessmentService.GetAllSupervisorsForSelfAssessmentId(SelfAssessmentId, DelegateUserId)) + .Returns(supervisors); + + // When + var result = controller.ProcessAgreed(model); + + // Then + A.CallTo(() => selfAssessmentService.MarkProgressAgreed(SelfAssessmentId, DelegateUserId)).MustHaveHappened(); + result.Should().BeViewResult() + .WithViewName("SelfAssessments/SelfAssessmentDescription") + .Model.Should().BeEquivalentTo(new SelfAssessmentDescriptionViewModel(selfAssessment, supervisors)); + } } } diff --git a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs index 5d3004f011..47757a9a5a 100644 --- a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs +++ b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs @@ -75,9 +75,45 @@ public IActionResult SelfAssessment(int selfAssessmentId) delegateUserId ).ToList(); var model = new SelfAssessmentDescriptionViewModel(selfAssessment, supervisors); + var isProcessAgreed = model.SelfAssessmentProcessAgreed; + if (!isProcessAgreed && selfAssessment.IsSupervised) + { + var processmodel = new SelfAssessmentProcessViewModel() + { + SelfAssessmentID = selfAssessmentId + }; + return View("SelfAssessments/AgreeSelfAssessmentProcess", processmodel); + } return View("SelfAssessments/SelfAssessmentDescription", model); } + [HttpPost] + public IActionResult ProcessAgreed(SelfAssessmentProcessViewModel model) + { + if (!ModelState.IsValid) + { + return View("SelfAssessments/AgreeSelfAssessmentProcess", model); + } + var delegateUserId = User.GetUserIdKnownNotNull(); + int selfAssessmentId = model.SelfAssessmentID; + var selfAssessment = selfAssessmentService.GetSelfAssessmentForCandidateById(delegateUserId, selfAssessmentId); + if (selfAssessment == null) + { + logger.LogWarning( + $"Attempt to display self assessment description for user {delegateUserId} with no self assessment" + ); + return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); + } + var supervisors = selfAssessmentService.GetAllSupervisorsForSelfAssessmentId( + selfAssessmentId, + delegateUserId + ).ToList(); + var selfAssessmentDescriptionViewModel = new SelfAssessmentDescriptionViewModel(selfAssessment, supervisors); + selfAssessmentService.MarkProgressAgreed(selfAssessmentId, delegateUserId); + return View("SelfAssessments/SelfAssessmentDescription", selfAssessmentDescriptionViewModel); + + } + [ServiceFilter(typeof(IsCentreAuthorizedSelfAssessment))] [Route("/LearningPortal/SelfAssessment/{selfAssessmentId:int}/{competencyNumber:int}")] public IActionResult SelfAssessmentCompetency(int selfAssessmentId, int competencyNumber) diff --git a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs index c9a50014fa..e0890e32ec 100644 --- a/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs +++ b/DigitalLearningSolutions.Web/Services/SelfAssessmentService.cs @@ -32,6 +32,7 @@ public interface ISelfAssessmentService void SetCompleteByDate(int selfAssessmentId, int delegateUserId, DateTime? completeByDate); + void MarkProgressAgreed(int selfAssessmentId, int delegateUserId); bool CanDelegateAccessSelfAssessment(int delegateUserId, int selfAssessmentId, int centreId); // Competencies @@ -216,6 +217,11 @@ public void SetCompleteByDate(int selfAssessmentId, int delegateUserId, DateTime selfAssessmentDataService.SetCompleteByDate(selfAssessmentId, delegateUserId, completeByDate); } + public void MarkProgressAgreed(int selfAssessmentId, int delegateUserId) + { + selfAssessmentDataService.MarkProgressAgreed(selfAssessmentId, delegateUserId); + } + public IEnumerable GetCandidateAssessmentResultsById(int candidateAssessmentId, int adminId, int? selfAssessmentResultId = null) { return selfAssessmentDataService.GetCandidateAssessmentResultsById(candidateAssessmentId, adminId, selfAssessmentResultId); diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentDescriptionViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentDescriptionViewModel.cs index eb11408029..e4f7c1d11b 100644 --- a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentDescriptionViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentDescriptionViewModel.cs @@ -18,6 +18,7 @@ public class SelfAssessmentDescriptionViewModel public readonly string VocabPlural; public readonly string? Vocabulary; public readonly bool NonReportable; + public bool SelfAssessmentProcessAgreed { get; set; } public SelfAssessmentDescriptionViewModel( CurrentSelfAssessment selfAssessment, @@ -37,6 +38,7 @@ List supervisors Vocabulary = selfAssessment.Vocabulary; VocabPlural = FrameworkVocabularyHelper.VocabularyPlural(selfAssessment.Vocabulary); NonReportable = selfAssessment.NonReportable; + SelfAssessmentProcessAgreed = selfAssessment.SelfAssessmentProcessAgreed; } public List Supervisors { get; set; } diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentProcessViewModel.cs new file mode 100644 index 0000000000..9de6ad45eb --- /dev/null +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentProcessViewModel.cs @@ -0,0 +1,14 @@ +namespace DigitalLearningSolutions.Web.ViewModels.LearningPortal.SelfAssessments +{ + using System.Collections.Generic; + using DigitalLearningSolutions.Data.Models.SelfAssessments; + using DigitalLearningSolutions.Web.Attributes; + using DigitalLearningSolutions.Web.Helpers; + + public class SelfAssessmentProcessViewModel + { + public int SelfAssessmentID { get; set; } + [BooleanMustBeTrue(ErrorMessage = "Please tick the checkbox to confirm that you understand and agree to the self-assessment process")] + public bool ActionConfirmed { get; set; } + } +} diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/AgreeSelfAssessmentProcess.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/AgreeSelfAssessmentProcess.cshtml new file mode 100644 index 0000000000..ca72fb82e9 --- /dev/null +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/AgreeSelfAssessmentProcess.cshtml @@ -0,0 +1,55 @@ +@using DigitalLearningSolutions.Web.Extensions +@using DigitalLearningSolutions.Web.ViewModels.LearningPortal.SelfAssessments; +@model SelfAssessmentProcessViewModel +@{ + var errorHasOccurred = !ViewData.ModelState.IsValid; + ViewData["Title"] = (errorHasOccurred ? "Error: " : "") + "Agree To Process"; + ViewData["Application"] = "LearningPortal"; + ViewData["HeaderPathName"] = "LearningPortal"; +} + +@section NavMenuItems { + +} + +
+
+ @if (errorHasOccurred) + { + + } +

How are proficiencies and frameworks assessed?

+ +

The process is learner-driven but you must be assessed by a supervisor before completing a self-assessment.

+ +

Once enrolled in a framework with at least 1 supervisor added, you can complete a digital self-assessment for any proficiency. However, before doing so, ensure that:

+ +
    +
  1. Your supervisor can sign-off this proficiency based on their competence or department requirements.
  2. +
  3. You have been formally assessed, this could be through observation, discussion, or another agreed method with your supervisor.
  4. +
  5. You have reflected on the proficiency description and feel you meet the requirements.
  6. +
  7. If you don’t fully meet the requirements, you can still document your progress and next steps for future assessment.
  8. +
+ +
+
+ +
+ + +
+ +
+
From 378b7b6a2ea3cd684cd349a80772ec70a5332959 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Tue, 19 Aug 2025 08:52:32 +0100 Subject: [PATCH 2/3] TD-5339 Added migration nr a addiding DB column. --- ...08190845_AddSelfAssessmentProcessAgreed .cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 DigitalLearningSolutions.Data.Migrations/202508190845_AddSelfAssessmentProcessAgreed .cs diff --git a/DigitalLearningSolutions.Data.Migrations/202508190845_AddSelfAssessmentProcessAgreed .cs b/DigitalLearningSolutions.Data.Migrations/202508190845_AddSelfAssessmentProcessAgreed .cs new file mode 100644 index 0000000000..97ea3eeae0 --- /dev/null +++ b/DigitalLearningSolutions.Data.Migrations/202508190845_AddSelfAssessmentProcessAgreed .cs @@ -0,0 +1,18 @@ +namespace DigitalLearningSolutions.Data.Migrations +{ + using FluentMigrator; + + [Migration(202508190845)] + public class AddSelfAssessmentProcessAgreed : Migration + { + public override void Up() + { + Alter.Table("CandidateAssessments").AddColumn("SelfAssessmentProcessAgreed").AsDateTime().Nullable(); + } + + public override void Down() + { + Delete.Column("SelfAssessmentProcessAgreed").FromTable("CandidateAssessments"); + } + } +} From c4b040bcb94ddcc33d6807acf1f2237823a96681 Mon Sep 17 00:00:00 2001 From: Rohit Shrivastava Date: Tue, 19 Aug 2025 15:35:55 +0100 Subject: [PATCH 3/3] TD-5339 Changing text to pick up dynamic vocabulary --- .../SelfAssessment.cs | 21 +++++++++++-------- .../SelfAssessmentProcessViewModel.cs | 4 ++++ .../AgreeSelfAssessmentProcess.cshtml | 10 +++++---- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs index 47757a9a5a..4e9fe31021 100644 --- a/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs +++ b/DigitalLearningSolutions.Web/Controllers/LearningPortalController/SelfAssessment.cs @@ -68,6 +68,17 @@ public IActionResult SelfAssessment(int selfAssessmentId) return RedirectToAction("StatusCode", "LearningSolutions", new { code = 403 }); } + if (!selfAssessment.SelfAssessmentProcessAgreed && selfAssessment.IsSupervised) + { + var processmodel = new SelfAssessmentProcessViewModel() + { + SelfAssessmentID = selfAssessmentId, + Vocabulary = selfAssessment.Vocabulary, + VocabPlural = FrameworkVocabularyHelper.VocabularyPlural(selfAssessment.Vocabulary) + }; + return View("SelfAssessments/AgreeSelfAssessmentProcess", processmodel); + } + selfAssessmentService.IncrementLaunchCount(selfAssessmentId, delegateUserId); selfAssessmentService.UpdateLastAccessed(selfAssessmentId, delegateUserId); var supervisors = selfAssessmentService.GetAllSupervisorsForSelfAssessmentId( @@ -75,15 +86,7 @@ public IActionResult SelfAssessment(int selfAssessmentId) delegateUserId ).ToList(); var model = new SelfAssessmentDescriptionViewModel(selfAssessment, supervisors); - var isProcessAgreed = model.SelfAssessmentProcessAgreed; - if (!isProcessAgreed && selfAssessment.IsSupervised) - { - var processmodel = new SelfAssessmentProcessViewModel() - { - SelfAssessmentID = selfAssessmentId - }; - return View("SelfAssessments/AgreeSelfAssessmentProcess", processmodel); - } + return View("SelfAssessments/SelfAssessmentDescription", model); } diff --git a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentProcessViewModel.cs b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentProcessViewModel.cs index 9de6ad45eb..0a63eda050 100644 --- a/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentProcessViewModel.cs +++ b/DigitalLearningSolutions.Web/ViewModels/LearningPortal/SelfAssessments/SelfAssessmentProcessViewModel.cs @@ -10,5 +10,9 @@ public class SelfAssessmentProcessViewModel public int SelfAssessmentID { get; set; } [BooleanMustBeTrue(ErrorMessage = "Please tick the checkbox to confirm that you understand and agree to the self-assessment process")] public bool ActionConfirmed { get; set; } + + public string? VocabPlural { get; set; } + public string? Vocabulary { get; set; } + } } diff --git a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/AgreeSelfAssessmentProcess.cshtml b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/AgreeSelfAssessmentProcess.cshtml index ca72fb82e9..02fef29335 100644 --- a/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/AgreeSelfAssessmentProcess.cshtml +++ b/DigitalLearningSolutions.Web/Views/LearningPortal/SelfAssessments/AgreeSelfAssessmentProcess.cshtml @@ -18,16 +18,16 @@ { } -

How are proficiencies and frameworks assessed?

+

How are @Model.VocabPlural?.ToLower() and frameworks assessed?

The process is learner-driven but you must be assessed by a supervisor before completing a self-assessment.

-

Once enrolled in a framework with at least 1 supervisor added, you can complete a digital self-assessment for any proficiency. However, before doing so, ensure that:

+

Once enrolled in a framework with at least 1 supervisor added, you can complete a digital self-assessment for any @Model.Vocabulary?.ToLower(). However, before doing so, ensure that:

    -
  1. Your supervisor can sign-off this proficiency based on their competence or department requirements.
  2. +
  3. Your supervisor can sign-off this @Model.Vocabulary?.ToLower() based on their competence or department requirements.
  4. You have been formally assessed, this could be through observation, discussion, or another agreed method with your supervisor.
  5. -
  6. You have reflected on the proficiency description and feel you meet the requirements.
  7. +
  8. You have reflected on the @Model.Vocabulary?.ToLower() description and feel you meet the requirements.
  9. If you don’t fully meet the requirements, you can still document your progress and next steps for future assessment.
@@ -41,6 +41,8 @@ Continue + +