Improve dynamic question learning loop

This commit is contained in:
hz
2026-07-23 16:50:53 -05:00
parent c91e70deb5
commit 32c455bba0
14 changed files with 422 additions and 16 deletions
@@ -317,6 +317,11 @@ public sealed class DynamicQuestionGenerator
if (string.IsNullOrWhiteSpace(blueprint.ExternalId)) return "externalId required";
if (string.IsNullOrWhiteSpace(blueprint.ObjectiveId)) return "objectiveId required";
if (blueprint.Version < 1) return "version must be positive";
if (blueprint.BlueprintWeight < 0) return "blueprintWeight cannot be negative";
if (blueprint.IsPublished
&& blueprint.MedicalReviewExpiresUtc is { } expiresUtc
&& expiresUtc <= DateTime.UtcNow)
return "medical review has expired";
if (blueprint.QualityStandardVersion >= 2)
return ValidateV2(blueprint);
if (string.IsNullOrWhiteSpace(blueprint.StemTemplate) || string.IsNullOrWhiteSpace(blueprint.LeadIn)) return "stemTemplate and leadIn required";
@@ -355,6 +360,22 @@ public sealed class DynamicQuestionGenerator
if (blueprint.CaseFamilies.Any(value => !DynamicDifficultyPolicy.TierElos.ContainsKey(value.DifficultyTier)))
return "difficultyTier must be foundation, standard, advanced, or expert";
}
if (blueprint.QualityStandardVersion >= 4)
{
if (blueprint.BlueprintWeight <= 0) return "quality standard 4 requires blueprintWeight";
if (string.IsNullOrWhiteSpace(blueprint.System)
|| string.IsNullOrWhiteSpace(blueprint.PhysicianTask)
|| string.IsNullOrWhiteSpace(blueprint.EncounterFrame)
|| string.IsNullOrWhiteSpace(blueprint.SiteOfCare))
return "quality standard 4 requires system, physicianTask, encounterFrame, and siteOfCare";
if (blueprint.ConceptIds.Count == 0) return "quality standard 4 requires conceptIds";
if (blueprint.MedicallyReviewedUtc is null
|| blueprint.MedicalReviewExpiresUtc is null
|| string.IsNullOrWhiteSpace(blueprint.MedicalReviewer))
return "quality standard 4 requires medical review metadata";
if (blueprint.Distractors.Any(value => string.IsNullOrWhiteSpace(value.MisconceptionId)))
return "quality standard 4 requires a misconceptionId for every distractor";
}
foreach (var family in blueprint.CaseFamilies)
{
if (string.IsNullOrWhiteSpace(family.Id)) return "case family id required";
@@ -367,6 +388,9 @@ public sealed class DynamicQuestionGenerator
var reviews = family.ReviewFollowups.Count == 0 ? blueprint.ReviewFollowups : family.ReviewFollowups;
var wrong = family.WrongAnswerFollowups.Count == 0 ? blueprint.WrongAnswerFollowups : family.WrongAnswerFollowups;
if (distractors.Count < 6) return $"{family.Id} requires at least six distractors";
if (blueprint.QualityStandardVersion >= 4
&& distractors.Any(value => string.IsNullOrWhiteSpace(value.MisconceptionId)))
return $"{family.Id} every distractor requires a misconceptionId";
var answers = distractors.Append(correctAnswer).ToList();
if (answers.Any(value => string.IsNullOrWhiteSpace(value.Id)
|| string.IsNullOrWhiteSpace(value.TextTemplate)