76 lines
4.1 KiB
C#
76 lines
4.1 KiB
C#
using Microsoft.Extensions.Configuration;
|
|
using QuestionsApi.Models;
|
|
using System.Text.Json;
|
|
|
|
namespace QuestionsApi.Services;
|
|
|
|
public static class DynamicReviewExportCommand
|
|
{
|
|
public static async Task<int> RunAsync(string[] args, TextWriter output)
|
|
{
|
|
static string Argument(string[] values, string name, string fallback = "")
|
|
{
|
|
var index = Array.FindIndex(values, value => value.Equals(name, StringComparison.OrdinalIgnoreCase));
|
|
return index >= 0 && index + 1 < values.Length ? values[index + 1] : fallback;
|
|
}
|
|
var source = Argument(args, "--source", Path.Combine(AppContext.BaseDirectory, "content", "dynamic_questions"));
|
|
var destination = Argument(args, "--output");
|
|
if (!Directory.Exists(source) || string.IsNullOrWhiteSpace(destination))
|
|
{
|
|
await output.WriteLineAsync("Usage: export-dynamic-review --source <blueprints> --output <directory>");
|
|
return 2;
|
|
}
|
|
var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web)
|
|
{ PropertyNameCaseInsensitive = true, WriteIndented = true };
|
|
var blueprints = new List<DynamicQuestionBlueprint>();
|
|
foreach (var file in Directory.EnumerateFiles(source, "*.json", SearchOption.AllDirectories))
|
|
blueprints.AddRange(JsonSerializer.Deserialize<List<DynamicQuestionBlueprint>>(
|
|
await File.ReadAllTextAsync(file), jsonOptions) ?? []);
|
|
blueprints = DynamicBlueprintResolver.ResolveAll(blueprints);
|
|
blueprints = blueprints.Where(value => value.IsPublished && value.QualityStandardVersion >= 2).ToList();
|
|
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddJsonFile("appsettings.json", optional: true)
|
|
.AddEnvironmentVariables()
|
|
.Build();
|
|
var authored = (await new QuestionStore(new MongoDatabaseProvider(configuration)).GetPublishedAsync())
|
|
.OrderBy(value => value.ExternalId, StringComparer.Ordinal)
|
|
.Take(blueprints.Count * 3)
|
|
.ToList();
|
|
if (authored.Count < blueprints.Count * 3)
|
|
throw new InvalidOperationException("Not enough published authored questions for a balanced review packet.");
|
|
|
|
var generator = new DynamicQuestionGenerator();
|
|
var entries = new List<(Question Question, string Source, string Detail)>();
|
|
foreach (var blueprint in blueprints)
|
|
for (var seed = 0; seed < 3; seed++)
|
|
{
|
|
var generated = generator.Generate(blueprint, $"review-{seed}", $"review-{blueprint.ExternalId}-{seed}");
|
|
entries.Add((generated.Question, "dynamic", $"{blueprint.ExternalId}:{generated.CaseFamilyId}"));
|
|
}
|
|
entries.AddRange(authored.Select(value => (value, "authored", value.ExternalId)));
|
|
var ordered = entries.OrderBy(value => Convert.ToHexString(
|
|
System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(value.Detail)))).ToList();
|
|
|
|
Directory.CreateDirectory(destination);
|
|
var packet = ordered.Select((entry, index) => new
|
|
{
|
|
reviewId = $"Q{index + 1:D2}",
|
|
entry.Question.Stem,
|
|
entry.Question.LeadIn,
|
|
entry.Question.Choices,
|
|
entry.Question.Labs,
|
|
entry.Question.Explanation,
|
|
entry.Question.ChoiceExplanations,
|
|
entry.Question.ReviewFollowups,
|
|
entry.Question.WrongAnswerFollowups,
|
|
scoring = new { realism = 0, distractors = 0, explanation = 0, step3Relevance = 0, criticalIssue = "", predictedSource = "" }
|
|
});
|
|
var key = ordered.Select((entry, index) => new { reviewId = $"Q{index + 1:D2}", entry.Source, entry.Detail });
|
|
await File.WriteAllTextAsync(Path.Combine(destination, "review-packet.json"), JsonSerializer.Serialize(packet, jsonOptions));
|
|
await File.WriteAllTextAsync(Path.Combine(destination, "answer-key.json"), JsonSerializer.Serialize(key, jsonOptions));
|
|
await output.WriteLineAsync($"Exported {ordered.Count} blinded questions to {destination}.");
|
|
return 0;
|
|
}
|
|
}
|