Upgrade dynamic questions to authored parity
This commit is contained in:
@@ -48,6 +48,38 @@ public sealed class DynamicQuestionTests
|
||||
Assert.ThrowsAny<Exception>(() => service.ReadGeneration(token + "x", "user-one"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GoldGenerator_UsesCaseFamilies_AndProducesAuthoredTeachingShape()
|
||||
{
|
||||
var blueprint = GoldBlueprint();
|
||||
var generator = new DynamicQuestionGenerator();
|
||||
var generated = Enumerable.Range(0, 100)
|
||||
.Select(seed => generator.Generate(blueprint, seed.ToString(), seed.ToString()))
|
||||
.ToList();
|
||||
|
||||
Assert.Equal(3, generated.Select(value => value.CaseFamilyId).Distinct().Count());
|
||||
Assert.All(generated, value =>
|
||||
{
|
||||
Assert.True(value.Question.Stem.Length >= 220);
|
||||
Assert.Equal(5, value.Question.Choices.Count);
|
||||
Assert.Equal(5, value.Question.ChoiceExplanations.Count);
|
||||
Assert.Single(value.Question.ReviewFollowups);
|
||||
Assert.Equal(4, value.Question.WrongAnswerFollowups.Count);
|
||||
Assert.Contains("quality-standard:2", value.Question.Tags);
|
||||
});
|
||||
Assert.Null(DynamicQuestionGenerator.Validate(blueprint));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LegacyGeneratorVersion_RemainsDeterministic()
|
||||
{
|
||||
var generator = new DynamicQuestionGenerator();
|
||||
var first = generator.Generate(Blueprint(), "legacy-seed", "one", 1).Question;
|
||||
var regenerated = generator.Generate(Blueprint(), "legacy-seed", "two", 1).Question;
|
||||
Assert.Equal(first.Stem, regenerated.Stem);
|
||||
Assert.Equal(first.Choices.Select(value => value.Id), regenerated.Choices.Select(value => value.Id));
|
||||
}
|
||||
|
||||
private static DynamicQuestionBlueprint Blueprint() => new()
|
||||
{
|
||||
ExternalId = "test",
|
||||
@@ -68,4 +100,50 @@ public sealed class DynamicQuestionTests
|
||||
],
|
||||
References = [new QuestionReference { Title = "Reference", Url = "https://example.test" }]
|
||||
};
|
||||
|
||||
private static DynamicQuestionBlueprint GoldBlueprint()
|
||||
{
|
||||
var stem = string.Join(" ", Enumerable.Repeat(
|
||||
"A patient presents with a carefully correlated history, examination, vital signs, and diagnostic findings that establish the clinical context without naming the diagnosis.", 3));
|
||||
return new DynamicQuestionBlueprint
|
||||
{
|
||||
ExternalId = "gold-test",
|
||||
Version = 2,
|
||||
ObjectiveId = "gold.objective",
|
||||
QualityStandardVersion = 2,
|
||||
CorrectConceptId = "gold.correct",
|
||||
CorrectAnswer = new DynamicAnswerTemplate
|
||||
{ Id = "correct", TextTemplate = "Correct action", ExplanationTemplate = "This addresses the decisive finding." },
|
||||
Distractors = Enumerable.Range(1, 6).Select(index => new DynamicAnswerTemplate
|
||||
{
|
||||
Id = $"d{index}",
|
||||
TextTemplate = $"Distractor {index}",
|
||||
ExplanationTemplate = $"Distractor {index} does not address the decisive finding."
|
||||
}).ToList(),
|
||||
ExplanationTemplate = "A complete explanation connects the presentation to the action.",
|
||||
KeyTakeaway = "Use the decisive finding.",
|
||||
UsmlePearl = "Sequence urgent care correctly.",
|
||||
ReviewFollowups =
|
||||
[
|
||||
new DynamicReviewFollowupTemplate
|
||||
{
|
||||
PromptTemplate = "What is decisive?",
|
||||
Choices =
|
||||
[
|
||||
new() { Id = "yes", TextTemplate = "The unstable finding" },
|
||||
new() { Id = "no", TextTemplate = "An incidental finding" }
|
||||
],
|
||||
CorrectChoiceId = "yes",
|
||||
TeachingPointTemplate = "The unstable finding determines management."
|
||||
}
|
||||
],
|
||||
CaseFamilies =
|
||||
[
|
||||
new() { Id = "family-a", StemTemplate = stem, LeadIn = "What is next?" },
|
||||
new() { Id = "family-b", StemTemplate = stem + " Family B.", LeadIn = "What is next?" },
|
||||
new() { Id = "family-c", StemTemplate = stem + " Family C.", LeadIn = "What is next?" }
|
||||
],
|
||||
References = [new QuestionReference { Title = "Reference", Url = "https://example.test" }]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user