87 lines
4.0 KiB
C#
87 lines
4.0 KiB
C#
using MongoDB.Driver;
|
|
using QuestionsApi.Models;
|
|
|
|
namespace QuestionsApi.Services;
|
|
|
|
public sealed class MasteryAwardService
|
|
{
|
|
private readonly IMongoCollection<QuestionAttempt> _attempts;
|
|
private readonly IMongoCollection<QuestionReviewAttempt> _reviews;
|
|
private readonly IMongoCollection<ReviewQueueItem> _queue;
|
|
private readonly QuestionStore _questions;
|
|
private readonly CreditStore _credits;
|
|
private readonly TimeProvider _clock;
|
|
|
|
public MasteryAwardService(MongoDatabaseProvider mongo, QuestionStore questions, CreditStore credits, TimeProvider clock)
|
|
{
|
|
_attempts = mongo.Database.GetCollection<QuestionAttempt>("QuestionAttempts");
|
|
_reviews = mongo.Database.GetCollection<QuestionReviewAttempt>("QuestionReviewAttempts");
|
|
_queue = mongo.Database.GetCollection<ReviewQueueItem>("ReviewQueueItems");
|
|
_questions = questions;
|
|
_credits = credits;
|
|
_clock = clock;
|
|
}
|
|
|
|
public async Task PrepareAsync(string userId)
|
|
{
|
|
if (await _credits.IsMasteryBaselineAppliedAsync(userId)) return;
|
|
var states = await CurrentStatesAsync(userId);
|
|
await _credits.InitializeMasteryBaselineAsync(userId, states.SelectMany(BaselineOperations));
|
|
}
|
|
|
|
public async Task<List<CreditAward>> AwardPromotionsAsync(string userId)
|
|
{
|
|
var awards = new List<CreditAward>();
|
|
var applied = await _credits.GetAppliedOperationsAsync(userId);
|
|
foreach (var system in await CurrentStatesAsync(userId))
|
|
{
|
|
foreach (var state in EarnedStates(system.MasteryState))
|
|
{
|
|
var spec = AwardSpec(state);
|
|
if (applied.Contains(Operation(system.System, state))) continue;
|
|
var result = await _credits.AwardAsync(userId, new CreditAwardRequest(
|
|
Operation(system.System, state), spec.Amount, spec.Reason, spec.Description,
|
|
"step3_system", system.System, false));
|
|
if (result.Award is not null) awards.Add(result.Award);
|
|
applied.Add(Operation(system.System, state));
|
|
}
|
|
}
|
|
return awards;
|
|
}
|
|
|
|
private async Task<List<DashboardSystemPerformance>> CurrentStatesAsync(string userId)
|
|
{
|
|
var attempts = await _attempts.Find(value => value.UserId == userId).ToListAsync();
|
|
var reviews = await _reviews.Find(value => value.UserId == userId).ToListAsync();
|
|
var queue = await _queue.Find(value => value.UserId == userId).ToListAsync();
|
|
var ids = attempts.Select(value => value.QuestionExternalId)
|
|
.Concat(reviews.Select(value => value.QuestionExternalId))
|
|
.Concat(queue.Select(value => value.QuestionExternalId));
|
|
var questions = await _questions.GetByExternalIdsAsync(ids);
|
|
return MasteryCalculator.Calculate(attempts, reviews, queue, questions, _clock.GetUtcNow().UtcDateTime);
|
|
}
|
|
|
|
private static IEnumerable<string> BaselineOperations(DashboardSystemPerformance system) =>
|
|
EarnedStates(system.MasteryState).Select(state => Operation(system.System, state));
|
|
|
|
private static IEnumerable<string> EarnedStates(string state)
|
|
{
|
|
if (state is MasteryStates.Practicing or MasteryStates.Strong or MasteryStates.Mastered)
|
|
yield return MasteryStates.Practicing;
|
|
if (state is MasteryStates.Strong or MasteryStates.Mastered)
|
|
yield return MasteryStates.Strong;
|
|
if (state is MasteryStates.Mastered)
|
|
yield return MasteryStates.Mastered;
|
|
}
|
|
|
|
private static string Operation(string system, string state) =>
|
|
$"mastery:{system.ToLowerInvariant().Replace(' ', '_')}:{state}";
|
|
|
|
private static (int Amount, string Reason, string Description) AwardSpec(string state) => state switch
|
|
{
|
|
MasteryStates.Practicing => (3, CreditReasonCodes.MasteryPracticing, "System advanced to Practicing"),
|
|
MasteryStates.Strong => (5, CreditReasonCodes.MasteryStrong, "System advanced to Strong"),
|
|
_ => (10, CreditReasonCodes.MasteryMastered, "System advanced to Mastered")
|
|
};
|
|
}
|