138 lines
6.0 KiB
C#
138 lines
6.0 KiB
C#
using System.CommandLine;
|
|
using System.Text.Json;
|
|
using Spectre.Console;
|
|
using VigilCare.Simulation;
|
|
|
|
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;
|
|
}
|
|
}
|