using System.CommandLine; using System.Text.Json; using Spectre.Console; public static class MimicGenerateCommand { public static Command Create() { var dataDirArg = new Argument("mimic-dir", "Path to directory containing MIMIC-IV CSV files"); var stayIdOpt = new Option("--stay-id", "ICU stay ID to generate scenario for") { IsRequired = true }; var maxHoursOpt = new Option("--max-hours", "Limit scenario duration in hours"); var noMedsOpt = new Option("--no-medications", () => false, "Exclude medication events"); var noLabsOpt = new Option("--no-labs", () => false, "Exclude lab observations"); var outputOpt = new Option("--output", "Output file path (default: auto-named)"); var validateOpt = new Option("--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; } }