Files

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")
};
}