Add variable question distractor pools
This commit is contained in:
@@ -7,7 +7,8 @@ public sealed class QuestionAttemptCounter
|
||||
public string QuestionExternalId { get; set; } = "";
|
||||
public int Bucket { get; set; }
|
||||
public string ChoiceId { get; set; } = "";
|
||||
public long Count { get; set; }
|
||||
public long SelectionCount { get; set; }
|
||||
public long PresentationCount { get; set; }
|
||||
public DateTime UpdatedUtc { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
@@ -15,14 +16,16 @@ public sealed class QuestionAttemptCounterTotal
|
||||
{
|
||||
[BsonId]
|
||||
public QuestionChoiceCounterKey Key { get; set; } = new();
|
||||
public long Count { get; set; }
|
||||
public long SelectionCount { get; set; }
|
||||
public long PresentationCount { get; set; }
|
||||
}
|
||||
|
||||
public sealed class QuestionAttemptCounterAggregate
|
||||
{
|
||||
[BsonId]
|
||||
public QuestionChoiceBucketKey Key { get; set; } = new();
|
||||
public long Count { get; set; }
|
||||
public long SelectionCount { get; set; }
|
||||
public long PresentationCount { get; set; }
|
||||
}
|
||||
|
||||
public sealed class QuestionChoiceBucketKey
|
||||
@@ -41,9 +44,18 @@ public sealed class QuestionChoiceCounterKey
|
||||
public sealed class AnswerStatisticsState
|
||||
{
|
||||
[BsonId]
|
||||
public string Id { get; set; } = "answer-statistics-v1";
|
||||
public string Id { get; set; } = "answer-statistics-v2";
|
||||
public bool Completed { get; set; }
|
||||
public string LeaseOwner { get; set; } = "";
|
||||
public DateTime LeaseUntilUtc { get; set; }
|
||||
public DateTime UpdatedUtc { get; set; } = DateTime.UtcNow;
|
||||
public DateTime CutoverUtc { get; set; }
|
||||
}
|
||||
|
||||
public sealed class QuestionPresentationBaseline
|
||||
{
|
||||
[BsonId]
|
||||
public string QuestionExternalId { get; set; } = "";
|
||||
public List<string> ChoiceIds { get; set; } = [];
|
||||
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace QuestionsApi.Models;
|
||||
|
||||
public sealed class QuestionAdminExportResponse
|
||||
{
|
||||
public List<QuestionResponse> Items { get; set; } = [];
|
||||
public string? NextCursor { get; set; }
|
||||
public bool HasMore { get; set; }
|
||||
}
|
||||
@@ -12,6 +12,8 @@ public class QuestionAttempt
|
||||
public string QuestionExternalId { get; set; } = "";
|
||||
public string UserId { get; set; } = "";
|
||||
public string ChoiceId { get; set; } = "";
|
||||
public List<string> PresentedChoiceIds { get; set; } = [];
|
||||
public bool TracksPresentation { get; set; }
|
||||
public bool IsCorrect { get; set; }
|
||||
public bool IsSkip { get; set; }
|
||||
public int CounterBucket { get; set; }
|
||||
|
||||
@@ -8,6 +8,7 @@ public class QuestionSearchRequest
|
||||
public int? DifficultyMin { get; set; }
|
||||
public int? DifficultyMax { get; set; }
|
||||
public int Limit { get; set; } = 40;
|
||||
public bool SupportsChoicePools { get; set; }
|
||||
}
|
||||
|
||||
public class UpsertQuestionRequest
|
||||
@@ -35,4 +36,5 @@ public class FirstQuestionAttemptRequest
|
||||
{
|
||||
public string QuestionExternalId { get; set; } = "";
|
||||
public string ChoiceId { get; set; } = "";
|
||||
public List<string> PresentedChoiceIds { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public class QuestionResponse
|
||||
public Dictionary<string, string> ChoiceExplanations { get; set; } = [];
|
||||
public Dictionary<string, WrongAnswerFollowup> WrongAnswerFollowups { get; set; } = [];
|
||||
public Dictionary<string, int> ChoiceSelectionPercentages { get; set; } = [];
|
||||
public string ChoiceSelectionRateBasis { get; set; } = "presented";
|
||||
public string Discipline { get; set; } = "";
|
||||
public string System { get; set; } = "";
|
||||
public string PerformanceCategory { get; set; } = "";
|
||||
@@ -24,27 +25,56 @@ public class QuestionResponse
|
||||
public static QuestionResponse FromModel(
|
||||
Question question,
|
||||
Dictionary<string, int>? liveChoiceSelectionPercentages = null,
|
||||
int questionElo = 1000) => new()
|
||||
int questionElo = 1000,
|
||||
bool includeChoicePool = true)
|
||||
{
|
||||
Id = question.Id ?? "",
|
||||
ExternalId = question.ExternalId,
|
||||
Stem = question.Stem,
|
||||
LeadIn = question.LeadIn,
|
||||
Choices = question.Choices,
|
||||
CorrectChoiceId = question.CorrectChoiceId,
|
||||
Explanation = question.Explanation,
|
||||
ChoiceExplanations = question.ChoiceExplanations,
|
||||
WrongAnswerFollowups = question.WrongAnswerFollowups,
|
||||
ChoiceSelectionPercentages = BuildChoiceSelectionPercentages(question, liveChoiceSelectionPercentages),
|
||||
Discipline = question.Discipline,
|
||||
System = question.System,
|
||||
PerformanceCategory = RatingCategory.Normalize(question.PerformanceCategory, question.System, question.Discipline),
|
||||
Difficulty = question.Difficulty,
|
||||
QuestionElo = questionElo,
|
||||
Labs = question.Labs,
|
||||
Media = question.Media,
|
||||
Tags = question.Tags
|
||||
};
|
||||
var choices = includeChoicePool ? question.Choices : SelectLegacyChoices(question);
|
||||
var choiceIds = choices.Select(choice => choice.Id).ToHashSet(StringComparer.Ordinal);
|
||||
var percentages = BuildChoiceSelectionPercentages(question, liveChoiceSelectionPercentages);
|
||||
return new QuestionResponse
|
||||
{
|
||||
Id = question.Id ?? "",
|
||||
ExternalId = question.ExternalId,
|
||||
Stem = question.Stem,
|
||||
LeadIn = question.LeadIn,
|
||||
Choices = choices,
|
||||
CorrectChoiceId = question.CorrectChoiceId,
|
||||
Explanation = question.Explanation,
|
||||
ChoiceExplanations = question.ChoiceExplanations
|
||||
.Where(entry => choiceIds.Contains(entry.Key))
|
||||
.ToDictionary(),
|
||||
WrongAnswerFollowups = question.WrongAnswerFollowups
|
||||
.Where(entry => choiceIds.Contains(entry.Key))
|
||||
.ToDictionary(),
|
||||
ChoiceSelectionPercentages = percentages
|
||||
.Where(entry => choiceIds.Contains(entry.Key))
|
||||
.ToDictionary(),
|
||||
Discipline = question.Discipline,
|
||||
System = question.System,
|
||||
PerformanceCategory = RatingCategory.Normalize(question.PerformanceCategory, question.System, question.Discipline),
|
||||
Difficulty = question.Difficulty,
|
||||
QuestionElo = questionElo,
|
||||
Labs = question.Labs,
|
||||
Media = question.Media,
|
||||
Tags = question.Tags
|
||||
};
|
||||
}
|
||||
|
||||
private static List<QuestionChoice> SelectLegacyChoices(Question question)
|
||||
{
|
||||
if (question.Choices.Count <= 5)
|
||||
return question.Choices;
|
||||
|
||||
var correct = question.Choices.First(choice => choice.Id == question.CorrectChoiceId);
|
||||
var distractors = question.Choices
|
||||
.Where(choice => choice.Id != question.CorrectChoiceId)
|
||||
.OrderBy(_ => Random.Shared.Next())
|
||||
.Take(4)
|
||||
.Append(correct)
|
||||
.OrderBy(_ => Random.Shared.Next())
|
||||
.ToList();
|
||||
return distractors;
|
||||
}
|
||||
|
||||
public static Dictionary<string, int> BuildChoiceSelectionPercentages(Question question, Dictionary<string, int>? liveChoiceSelectionPercentages)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user