Files
step3/api/QuestionsApi/Services/DynamicReviewExportCommand.cs
T

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;
}
}