Add SUPER exam results preview

This commit is contained in:
hz
2026-07-14 17:25:25 -05:00
parent 51ce9f2e88
commit bbb373acf4
4 changed files with 109 additions and 0 deletions
@@ -93,6 +93,54 @@ public class ExamSimulationStore
return simulation;
}
public async Task<ExamSimulationStateResponse> BuildResultsPreviewAsync(string userId)
{
var bank = (await _questions.GetPublishedAsync()).Where(question => question.Choices.Count >= 5).ToList();
if (bank.Count < RequiredQuestions)
throw new InvalidOperationException($"At least {RequiredQuestions} published questions are required.");
var targets = BuildTargets();
var selected = SelectForm(bank, [], targets);
var items = selected.Select((question, index) => new ExamSimulationItem
{
QuestionExternalId = question.ExternalId,
BlockIndex = BlockIndexForPosition(index),
Position = PositionWithinBlock(index),
PresentedChoiceIds = SelectChoices(question),
QuestionElo = 1000,
WasSeen = false,
BlueprintCategory = BlueprintCategory(question)
}).ToList();
var questionMap = selected.ToDictionary(question => question.ExternalId, StringComparer.Ordinal);
var responses = items.Select((item, index) =>
{
var question = questionMap[item.QuestionExternalId];
var choiceId = index % 3 == 0
? item.PresentedChoiceIds.First(id => id != question.CorrectChoiceId)
: question.CorrectChoiceId;
return new ExamSimulationResponse
{
QuestionExternalId = item.QuestionExternalId,
ChoiceId = choiceId,
ElapsedSeconds = 75 + index % 45,
Flagged = index % 11 == 0
};
}).ToList();
var responseMap = responses.ToDictionary(item => item.QuestionExternalId, StringComparer.Ordinal);
return new ExamSimulationStateResponse
{
Id = "preview",
State = ExamSimulationStates.Completed,
CurrentBlock = BlockSizes.Length - 1,
BreakSecondsRemaining = 3300,
Responses = responses,
Score = ExamSimulationScoring.Calculate(items, responseMap, questionMap),
BlueprintTargets = targets,
BlueprintActual = items.GroupBy(item => item.BlueprintCategory).ToDictionary(group => group.Key, group => group.Count()),
ReviewQuestions = items.Select(item => QuestionResponse.FromModel(
questionMap[item.QuestionExternalId], questionElo: item.QuestionElo, includeChoicePool: true)).ToList()
};
}
public async Task<ExamSimulation?> FindActiveAsync(string userId) =>
await _simulations.Find(item => item.UserId == userId &&
(item.State == ExamSimulationStates.BetweenBlocks || item.State == ExamSimulationStates.BlockActive))
@@ -261,6 +309,28 @@ public class ExamSimulationStore
private Task ReplaceAsync(ExamSimulation simulation) =>
_simulations.ReplaceOneAsync(item => item.Id == simulation.Id && item.UserId == simulation.UserId, simulation);
private static int BlockIndexForPosition(int position)
{
var consumed = 0;
for (var block = 0; block < BlockSizes.Length; block++)
{
if (position < consumed + BlockSizes[block]) return block;
consumed += BlockSizes[block];
}
return BlockSizes.Length - 1;
}
private static int PositionWithinBlock(int position)
{
var consumed = 0;
foreach (var size in BlockSizes)
{
if (position < consumed + size) return position - consumed;
consumed += size;
}
return 0;
}
private static ExamSimulationQuestionResponse SafeQuestion(Question question, List<string> presentedIds) => new()
{
ExternalId = question.ExternalId,