Add variable question distractor pools
This commit is contained in:
@@ -52,10 +52,34 @@ public class QuestionsController : ControllerBase
|
||||
return Ok(questions.Select(q =>
|
||||
{
|
||||
percentages.TryGetValue(q.ExternalId, out var questionPercentages);
|
||||
return QuestionResponse.FromModel(q, questionPercentages, questionElos.GetValueOrDefault(q.ExternalId, 1000));
|
||||
return QuestionResponse.FromModel(
|
||||
q,
|
||||
questionPercentages,
|
||||
questionElos.GetValueOrDefault(q.ExternalId, 1000),
|
||||
req.SupportsChoicePools);
|
||||
}).ToList());
|
||||
}
|
||||
|
||||
[HttpGet("admin/export")]
|
||||
[Authorize(Roles = "SUPER")]
|
||||
public async Task<IActionResult> AdminExport([FromQuery] string? after = null, [FromQuery] int limit = 100)
|
||||
{
|
||||
try
|
||||
{
|
||||
var page = await _questions.ExportPageAsync(after, limit);
|
||||
return Ok(new QuestionAdminExportResponse
|
||||
{
|
||||
Items = page.Items.Select(question => QuestionResponse.FromModel(question)).ToList(),
|
||||
NextCursor = page.NextCursor,
|
||||
HasMore = page.HasMore
|
||||
});
|
||||
}
|
||||
catch (ArgumentException exception)
|
||||
{
|
||||
return BadRequest(exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
[Authorize(Roles = "USER,SUPER")]
|
||||
public async Task<IActionResult> Get(string id)
|
||||
@@ -84,6 +108,22 @@ public class QuestionsController : ControllerBase
|
||||
if (!question.Choices.Any(c => c.Id == req.ChoiceId.Trim()))
|
||||
return BadRequest("choiceId must match a choice id");
|
||||
|
||||
var presentedChoiceIds = req.PresentedChoiceIds
|
||||
.Where(id => !string.IsNullOrWhiteSpace(id))
|
||||
.Select(id => id.Trim())
|
||||
.ToList();
|
||||
if (presentedChoiceIds.Count > 0)
|
||||
{
|
||||
var validChoiceIds = question.Choices.Select(choice => choice.Id).ToHashSet(StringComparer.Ordinal);
|
||||
if (presentedChoiceIds.Count != 5 || presentedChoiceIds.Distinct(StringComparer.Ordinal).Count() != 5)
|
||||
return BadRequest("presentedChoiceIds must contain exactly five unique choice ids");
|
||||
if (presentedChoiceIds.Any(id => !validChoiceIds.Contains(id)))
|
||||
return BadRequest("presentedChoiceIds must match question choice ids");
|
||||
if (!presentedChoiceIds.Contains(req.ChoiceId.Trim(), StringComparer.Ordinal)
|
||||
|| !presentedChoiceIds.Contains(question.CorrectChoiceId, StringComparer.Ordinal))
|
||||
return BadRequest("presentedChoiceIds must include the selected and correct choices");
|
||||
}
|
||||
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? User.FindFirstValue(ClaimTypes.Name)
|
||||
?? User.Identity?.Name
|
||||
@@ -95,7 +135,9 @@ public class QuestionsController : ControllerBase
|
||||
question.ExternalId,
|
||||
userId,
|
||||
req.ChoiceId.Trim(),
|
||||
req.ChoiceId.Trim() == question.CorrectChoiceId);
|
||||
req.ChoiceId.Trim() == question.CorrectChoiceId,
|
||||
false,
|
||||
presentedChoiceIds);
|
||||
if (record.Inserted)
|
||||
{
|
||||
_metrics.AttemptCreated();
|
||||
@@ -252,7 +294,7 @@ public class QuestionsController : ControllerBase
|
||||
[Authorize(Roles = "SUPER")]
|
||||
public async Task<IActionResult> Create([FromBody] UpsertQuestionRequest req)
|
||||
{
|
||||
var validation = ValidateQuestion(req);
|
||||
var validation = QuestionValidator.Validate(req);
|
||||
if (validation is not null)
|
||||
return BadRequest(validation);
|
||||
|
||||
@@ -272,7 +314,7 @@ public class QuestionsController : ControllerBase
|
||||
|
||||
foreach (var question in req.Questions)
|
||||
{
|
||||
var validation = ValidateQuestion(question);
|
||||
var validation = QuestionValidator.Validate(question);
|
||||
if (validation is not null)
|
||||
return BadRequest($"{question.ExternalId}: {validation}");
|
||||
}
|
||||
@@ -285,7 +327,7 @@ public class QuestionsController : ControllerBase
|
||||
[Authorize(Roles = "SUPER")]
|
||||
public async Task<IActionResult> Update(string id, [FromBody] UpsertQuestionRequest req)
|
||||
{
|
||||
var validation = ValidateQuestion(req);
|
||||
var validation = QuestionValidator.Validate(req);
|
||||
if (validation is not null)
|
||||
return BadRequest(validation);
|
||||
|
||||
@@ -314,59 +356,6 @@ public class QuestionsController : ControllerBase
|
||||
return Ok("Deleted");
|
||||
}
|
||||
|
||||
private static string? ValidateQuestion(UpsertQuestionRequest req)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(req.ExternalId))
|
||||
return "externalId required";
|
||||
if (string.IsNullOrWhiteSpace(req.Stem))
|
||||
return "stem required";
|
||||
if (string.IsNullOrWhiteSpace(req.LeadIn))
|
||||
return "leadIn required";
|
||||
if (req.Choices.Count < 2)
|
||||
return "at least two choices required";
|
||||
if (string.IsNullOrWhiteSpace(req.CorrectChoiceId))
|
||||
return "correctChoiceId required";
|
||||
if (!req.Choices.Any(c => c.Id == req.CorrectChoiceId))
|
||||
return "correctChoiceId must match a choice id";
|
||||
if (string.IsNullOrWhiteSpace(req.Explanation))
|
||||
return "explanation required";
|
||||
if (req.ChoiceSelectionPercentages.Any(entry => entry.Value is < 0 or > 100))
|
||||
return "choiceSelectionPercentages values must be between 0 and 100";
|
||||
var followupValidation = ValidateWrongAnswerFollowups(req);
|
||||
if (followupValidation is not null)
|
||||
return followupValidation;
|
||||
if (string.IsNullOrWhiteSpace(req.System))
|
||||
return "system required";
|
||||
if (string.IsNullOrWhiteSpace(req.Discipline))
|
||||
return "discipline required";
|
||||
if (req.Difficulty is < 1 or > 5)
|
||||
return "difficulty must be between 1 and 5";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? ValidateWrongAnswerFollowups(UpsertQuestionRequest req)
|
||||
{
|
||||
var choiceIds = req.Choices.Select(c => c.Id).Where(id => !string.IsNullOrWhiteSpace(id)).ToHashSet();
|
||||
foreach (var entry in req.WrongAnswerFollowups)
|
||||
{
|
||||
if (!choiceIds.Contains(entry.Key))
|
||||
return "wrongAnswerFollowups keys must match a choice id";
|
||||
if (entry.Key == req.CorrectChoiceId)
|
||||
return "wrongAnswerFollowups should only target incorrect choices";
|
||||
if (string.IsNullOrWhiteSpace(entry.Value.Prompt))
|
||||
return "wrongAnswerFollowups prompt required";
|
||||
if (string.IsNullOrWhiteSpace(entry.Value.TeachingPoint))
|
||||
return "wrongAnswerFollowups teachingPoint required";
|
||||
if (entry.Value.Choices.Count == 0)
|
||||
return "wrongAnswerFollowups choices required";
|
||||
if (!entry.Value.Choices.Any(c => c.Id == entry.Value.CorrectChoiceId))
|
||||
return "wrongAnswerFollowups correctChoiceId must match a follow-up choice id";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string CurrentUserId() =>
|
||||
User.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? User.FindFirstValue(ClaimTypes.Name)
|
||||
|
||||
Reference in New Issue
Block a user