Skip to content

Commit

Permalink
Merge pull request #126 from Clean-CaDET/development
Browse files Browse the repository at this point in the history
[July 2024] Learning use case enhancement pack
  • Loading branch information
lukaDoric authored Aug 2, 2024
2 parents 38be644 + beb5eb1 commit 55bdec8
Show file tree
Hide file tree
Showing 104 changed files with 1,595 additions and 369 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Linq.Expressions;
using System.Text.Json;
using Tutor.BuildingBlocks.Core.EventSourcing;

namespace Tutor.BuildingBlocks.Core.Domain.EventSourcing;

public interface IEventQueryable<TEvent> where TEvent : DomainEvent
{
IEventQueryable<TEvent> After(DateTime moment);
IEventQueryable<TEvent> Before(DateTime moment);
IEventQueryable<TEvent> Where(Expression<Func<JsonDocument, bool>> condition);
List<TEvent> ToList();
List<T> ToList<T>() where T : TEvent;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Text.Json;
using Tutor.BuildingBlocks.Core.EventSourcing;

namespace Tutor.BuildingBlocks.Core.Domain.EventSourcing;

public interface IEventSerializer<TEvent> where TEvent : DomainEvent
{
JsonDocument Serialize(TEvent @event);
TEvent Deserialize(JsonDocument @event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Tutor.BuildingBlocks.Core.EventSourcing;
using Tutor.BuildingBlocks.Core.UseCases;

namespace Tutor.BuildingBlocks.Core.Domain.EventSourcing;

public interface IEventStore<TEvent> where TEvent : DomainEvent
{
void Save(EventSourcedAggregateRoot aggregate);
IEventQueryable<TEvent> Events { get; }
Task<PagedResult<TEvent>> GetEventsAsync(int page, int pageSize);
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Tutor.KnowledgeComponents.Infrastructure.Database.EventStore.DefaultEventSerializer;
namespace Tutor.BuildingBlocks.Infrastructure.Database.EventStore.DefaultEventSerializer;

/// <summary>
/// Implements a Dahomey.Json discriminator policy, where the discriminator value is defined
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using Dahomey.Json;
using System.Collections.Immutable;
using System.Text.Json;
using Tutor.BuildingBlocks.Core.Domain.EventSourcing;
using Tutor.BuildingBlocks.Core.EventSourcing;

namespace Tutor.KnowledgeComponents.Infrastructure.Database.EventStore.DefaultEventSerializer;
namespace Tutor.BuildingBlocks.Infrastructure.Database.EventStore.DefaultEventSerializer;

public class DefaultEventSerializer : IEventSerializer
public class DefaultEventSerializer<TEvent> : IEventSerializer<TEvent> where TEvent : DomainEvent
{
private readonly JsonSerializerOptions _options;

Expand All @@ -24,13 +25,13 @@ public DefaultEventSerializer(IImmutableDictionary<Type, string> eventRelatedTyp

public DefaultEventSerializer(IImmutableDictionary<Type, string> eventRelatedTypes) : this(eventRelatedTypes, "$discriminator") { }

public DomainEvent Deserialize(JsonDocument @event)
public JsonDocument Serialize(TEvent @event)
{
return @event.Deserialize<DomainEvent>(_options)!;
return JsonSerializer.SerializeToDocument(@event, _options);
}

public JsonDocument Serialize(DomainEvent @event)
public TEvent Deserialize(JsonDocument @event)
{
return JsonSerializer.SerializeToDocument(@event, _options);
return @event.Deserialize<TEvent>(_options)!;
}
}
Original file line number Diff line number Diff line change
@@ -1,67 +1,68 @@
using System.Linq.Expressions;
using System.Text.Json;
using Tutor.BuildingBlocks.Core.Domain.EventSourcing;
using Tutor.BuildingBlocks.Core.EventSourcing;

namespace Tutor.KnowledgeComponents.Infrastructure.Database.EventStore.Postgres;
namespace Tutor.BuildingBlocks.Infrastructure.Database.EventStore.Postgres;

internal class PostgresEventQueryable : IEventQueryable
public class PostgresEventQueryable<TEvent> : IEventQueryable<TEvent> where TEvent : DomainEvent
{
private readonly IEventSerializer _serializer;
private readonly IEventSerializer<TEvent> _serializer;

private IQueryable<StoredDomainEvent> EventSource { get; init; }
private IEnumerable<Expression<Func<JsonDocument, bool>>> Conditions { get; init; }

public PostgresEventQueryable(IQueryable<StoredDomainEvent> eventSource, IEventSerializer serializer)
public PostgresEventQueryable(IQueryable<StoredDomainEvent> eventSource, IEventSerializer<TEvent> serializer)
{
EventSource = eventSource;
Conditions = new List<Expression<Func<JsonDocument, bool>>>();

_serializer = serializer;
}

private PostgresEventQueryable(PostgresEventQueryable parent)
private PostgresEventQueryable(PostgresEventQueryable<TEvent> parent)
{
EventSource = parent.EventSource;
Conditions = parent.Conditions;

_serializer = parent._serializer;
}

public IEventQueryable After(DateTime moment)
public IEventQueryable<TEvent> After(DateTime moment)
{
return new PostgresEventQueryable(this)
return new PostgresEventQueryable<TEvent>(this)
{
EventSource = EventSource.Where(e => e.TimeStamp >= moment.ToUniversalTime())
};
}

public IEventQueryable Before(DateTime moment)
public IEventQueryable<TEvent> Before(DateTime moment)
{
return new PostgresEventQueryable(this)
return new PostgresEventQueryable<TEvent>(this)
{
EventSource = EventSource.Where(e => e.TimeStamp <= moment.ToUniversalTime())
};
}

public IEventQueryable Where(Expression<Func<JsonDocument, bool>> condition)
public IEventQueryable<TEvent> Where(Expression<Func<JsonDocument, bool>> condition)
{
return new PostgresEventQueryable(this)
return new PostgresEventQueryable<TEvent>(this)
{
Conditions = Conditions.Append(condition)
};
}

public List<DomainEvent> ToList()
public List<TEvent> ToList()
{
return ApplyConditions().ToList();
}

public List<T> ToList<T>() where T : DomainEvent
public List<T> ToList<T>() where T : TEvent
{
return ApplyConditions().OfType<T>().ToList();
}

private IEnumerable<DomainEvent> ApplyConditions()
private IEnumerable<TEvent> ApplyConditions()
{
IQueryable<JsonDocument> events = EventSource.Select(e => e.DomainEvent);
foreach (Expression<Func<JsonDocument, bool>> condition in Conditions)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.Text.Json;

namespace Tutor.KnowledgeComponents.Infrastructure.Database.EventStore.Postgres;
namespace Tutor.BuildingBlocks.Infrastructure.Database.EventStore.Postgres;

internal class StoredDomainEvent
public class StoredDomainEvent
{
public int Id { get; set; }
public string AggregateType { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<PackageReference Include="Castle.Core" Version="5.1.1" />
<PackageReference Include="Dahomey.Json" Version="1.12.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Tutor.Courses.API.Dtos;

public class UnitFeedbackRequestDto
{
public bool RequestKcFeedback { get; set; }
public bool RequestTaskFeedback { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Tutor.Courses.API.Dtos;

public class UnitProgressRatingDto
{
public int? LearnerId { get; set; }
public int KnowledgeUnitId { get; set; }
public int[] CompletedKcIds { get; set; } = Array.Empty<int>();
public int[] CompletedTaskIds { get; set; } = Array.Empty<int>();
public DateTime Created { get; set; }
public string Feedback { get; set; } = "";
public bool IsLearnerInitiated { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using FluentResults;
using Tutor.Courses.API.Dtos;

namespace Tutor.Courses.API.Public.Analysis;

public interface IUnitProgressRatingService
{
Result<UnitFeedbackRequestDto> ShouldRequestFeedback(int unitId, int learnerId);
Result<UnitProgressRatingDto> Create(UnitProgressRatingDto rating, int learnerId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class CourseOwnership : Entity
public Course Course { get; private set; }
public int InstructorId { get; private set; }

private CourseOwnership() {}
private CourseOwnership() { }

public CourseOwnership(Course course, int instructorId)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Tutor.Courses.Core.Domain.RepositoryInterfaces;

public interface IUnitProgressRatingRepository
{
List<UnitProgressRating> GetByUnitAndLearner(int unitId, int learnerId);
UnitProgressRating Create(UnitProgressRating rating);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class UnitEnrollment : Entity
public DateTime? BestBefore { get; private set; }
public EnrollmentStatus Status { get; internal set; }

private UnitEnrollment() {}
private UnitEnrollment() { }

public UnitEnrollment(int learnerId, DateTime availableFrom, DateTime? bestBefore, KnowledgeUnit knowledgeUnit)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Tutor.BuildingBlocks.Core.Domain;

namespace Tutor.Courses.Core.Domain;

public class UnitFeedbackRequest : ValueObject
{
public bool RequestKcFeedback { get; }
public bool RequestTaskFeedback { get; }

public UnitFeedbackRequest(bool requestKcFeedback, bool requestTaskFeedback)
{
RequestKcFeedback = requestKcFeedback;
RequestTaskFeedback = requestTaskFeedback;
}

protected override IEnumerable<object> GetEqualityComponents()
{
yield return RequestKcFeedback;
yield return RequestTaskFeedback;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using FluentResults;

namespace Tutor.Courses.Core.Domain;

public static class UnitFeedbackRequestor
{
public static Result<UnitFeedbackRequest> ShouldRequestFeedback(Tuple<int, int> kcMasteryCount, Tuple<int, int> taskProgressCount, List<UnitProgressRating> ratings)
{
var totalItemsCount = kcMasteryCount.Item1 + taskProgressCount.Item1;
var completedItemsCount = kcMasteryCount.Item2 + taskProgressCount.Item2;

var lastRatingTime = ratings.Where(r => !r.IsLearnerInitiated).MaxBy(r => r.Created)?.Created ?? DateTime.UtcNow;
var ratedKcsCount = ratings.SelectMany(r => r.CompletedKcIds).Distinct().Count();
var ratedTasksCount = ratings.SelectMany(r => r.CompletedTaskIds).Distinct().Count();
var ratedItemsCount = ratedKcsCount + ratedTasksCount;

if (ratedItemsCount >= completedItemsCount)
return new UnitFeedbackRequest(false, false);
if (HasSufficientlyProgressed(completedItemsCount, totalItemsCount, ratedItemsCount, lastRatingTime))
return new UnitFeedbackRequest(
kcMasteryCount.Item2 > ratedKcsCount,
taskProgressCount.Item2 > ratedTasksCount
);
return new UnitFeedbackRequest(false, false);
}

private static bool HasSufficientlyProgressed(int completedItemsCount, int totalItemsCount, int ratedItemsCount, DateTime lastRatingTime)
{
return completedItemsCount == totalItemsCount ||
completedItemsCount >= ratedItemsCount + 3 ||
(completedItemsCount == ratedItemsCount + 1 && HoursHavePassed(lastRatingTime, 1.5)) ||
(completedItemsCount == ratedItemsCount + 2 && HoursHavePassed(lastRatingTime, 1));
}

private static bool HoursHavePassed(DateTime lastRatingTime, double hours)
{
return DateTime.UtcNow - lastRatingTime > TimeSpan.FromHours(hours);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Tutor.BuildingBlocks.Core.Domain;

namespace Tutor.Courses.Core.Domain;

public class UnitProgressRating : Entity
{
public int LearnerId { get; private set; }
public int KnowledgeUnitId { get; private set; }
public DateTime Created { get; private set; } = DateTime.UtcNow;
public int[] CompletedKcIds { get; private set; } = Array.Empty<int>();
public int[] CompletedTaskIds { get; private set; } = Array.Empty<int>();
public string Feedback { get; private set; } = "";
public bool IsLearnerInitiated { get; private set; }

private UnitProgressRating() {}

public UnitProgressRating(int[] completedKcIds, int[] completedTaskIds, DateTime created, bool isLearnerInitiated)
{
CompletedKcIds = completedKcIds;
CompletedTaskIds = completedTaskIds;
Created = created;
IsLearnerInitiated = isLearnerInitiated;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ public CourseProfile()
CreateMap<Course, CourseDto>();
CreateMap<KnowledgeUnit, KnowledgeUnitDto>();
CreateMap<KnowledgeUnitDto, KnowledgeUnit>();

CreateMap<UnitProgressRatingDto, UnitProgressRating>().ReverseMap();
CreateMap<UnitFeedbackRequest, UnitFeedbackRequestDto>();
}
}
Loading

0 comments on commit 55bdec8

Please sign in to comment.