using MongoDB.Driver; using QuestionsApi.Models; namespace QuestionsApi.Services; public sealed class MasteryAwardService { private readonly IMongoCollection _attempts; private readonly IMongoCollection _reviews; private readonly IMongoCollection _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("QuestionAttempts"); _reviews = mongo.Database.GetCollection("QuestionReviewAttempts"); _queue = mongo.Database.GetCollection("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> AwardPromotionsAsync(string userId) { var awards = new List(); 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> 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 BaselineOperations(DashboardSystemPerformance system) => EarnedStates(system.MasteryState).Select(state => Operation(system.System, state)); private static IEnumerable 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") }; }