Files
vigilcare-clinical/VigilCare.Simulator/Mimic/MimicGenerateCommand.cs
T

137 lines
5.9 KiB
C#

using System.CommandLine;
using System.Text.Json;
using Spectre.Console;
public static class MimicGenerateCommand
{
public static Command Create()
{
var dataDirArg = new Argument<DirectoryInfo>("mimic-dir",
"Path to directory containing MIMIC-IV CSV files");
var stayIdOpt = new Option<int>("--stay-id", "ICU stay ID to generate scenario for")
{ IsRequired = true };
var maxHoursOpt = new Option<int?>("--max-hours", "Limit scenario duration in hours");
var noMedsOpt = new Option<bool>("--no-medications", () => false,
"Exclude medication events");
var noLabsOpt = new Option<bool>("--no-labs", () => false,
"Exclude lab observations");
var outputOpt = new Option<string?>("--output", "Output file path (default: auto-named)");
var validateOpt = new Option<bool>("--validate", () => false,
"Run scenario validation after generation");
var command = new Command("mimic-generate",
"Generate a VigilCare scenario JSON from a MIMIC-IV ICU stay")
{
dataDirArg, stayIdOpt, maxHoursOpt, noMedsOpt, noLabsOpt, outputOpt, validateOpt
};
command.SetHandler(context =>
{
var dataDir = context.ParseResult.GetValueForArgument(dataDirArg);
var stayId = context.ParseResult.GetValueForOption(stayIdOpt);
var maxHours = context.ParseResult.GetValueForOption(maxHoursOpt);
var noMeds = context.ParseResult.GetValueForOption(noMedsOpt);
var noLabs = context.ParseResult.GetValueForOption(noLabsOpt);
var outputPath = context.ParseResult.GetValueForOption(outputOpt);
var validate = context.ParseResult.GetValueForOption(validateOpt);
var loader = new MimicDataLoader(dataDir.FullName);
AnsiConsole.MarkupLine($"[bold]Loading MIMIC-IV data for stay {stayId}...[/]");
var stays = loader.LoadIcuStays();
var stay = stays.FirstOrDefault(s => s.StayId == stayId);
if (stay is null)
{
AnsiConsole.MarkupLine($"[red]ICU stay {stayId} not found.[/]");
var available = stays.Select(s => s.StayId).OrderBy(x => x).ToList();
AnsiConsole.MarkupLine($"Available stay IDs: {string.Join(", ", available.Take(20))}...");
return;
}
var admissions = loader.LoadAdmissions();
var admission = admissions.FirstOrDefault(a => a.HadmId == stay.HadmId);
if (admission is null)
{
AnsiConsole.MarkupLine($"[red]Admission {stay.HadmId} not found.[/]");
return;
}
var patients = loader.LoadPatients();
var patient = patients.FirstOrDefault(p => p.SubjectId == stay.SubjectId);
if (patient is null)
{
AnsiConsole.MarkupLine($"[red]Patient {stay.SubjectId} not found.[/]");
return;
}
AnsiConsole.MarkupLine(
$" Patient: [cyan]{patient.SubjectId}[/] ({patient.Gender}, ~{patient.AnchorAge}y)");
AnsiConsole.MarkupLine(
$" Stay: [cyan]{stay.StayId}[/] in {stay.FirstCareUnit}");
AnsiConsole.MarkupLine(
$" LOS: {stay.Los:F1} days ({stay.InTime:g} → {stay.OutTime:g})");
AnsiConsole.MarkupLine(
$" Outcome: {(admission.HospitalExpireFlag == 1 ? "[red]Expired[/]" : "Survived")}");
var options = new MimicGenerateOptions(
MaxHours: maxHours,
IncludeMedications: !noMeds,
IncludeLabs: !noLabs);
AnsiConsole.MarkupLine("\n[bold]Generating scenario...[/]");
var builder = new MimicScenarioBuilder(loader);
var (scenario, warnings) = builder.Build(stay, admission, patient, options);
foreach (var warning in warnings)
AnsiConsole.MarkupLine($" [yellow]WARNING:[/] {Markup.Escape(warning)}");
var obsCount = scenario.Events.Count(e => e.Type == "observation");
var medCount = scenario.Events.Count(e => e.Type == "medication");
AnsiConsole.MarkupLine(
$" Events: [green]{obsCount}[/] observations, [green]{medCount}[/] medications");
AnsiConsole.MarkupLine(
$" Duration: {scenario.Scenario.DurationMinutes} minutes " +
$"({scenario.Scenario.DurationMinutes / 60.0:F1} hours)");
if (validate)
{
var errors = ScenarioValidator.Validate(scenario);
if (errors.Count == 0)
{
AnsiConsole.MarkupLine(" [green]Validation: PASSED[/]");
}
else
{
AnsiConsole.MarkupLine($" [red]Validation: {errors.Count} error(s)[/]");
foreach (var err in errors)
AnsiConsole.MarkupLine($" [red]• {Markup.Escape(err)}[/]");
}
}
var jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
var json = JsonSerializer.Serialize(scenario, jsonOptions);
var filePath = outputPath
?? Path.Combine("Scenarios", "List", $"mimic-s{stayId}.json");
var dir = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
File.WriteAllText(filePath, json);
AnsiConsole.MarkupLine($"\n[bold green]Scenario written to:[/] {filePath}");
AnsiConsole.MarkupLine($" Replay with: [dim]dotnet run -- replay {filePath}[/]");
});
return command;
}
}