feature: Console Replay Simulator

This commit is contained in:
voltsrage
2026-06-19 15:22:14 +08:00
parent 835311dffc
commit abc781c9c0
50 changed files with 11158 additions and 612 deletions
@@ -0,0 +1,30 @@
using System.CommandLine;
public static class DryRunCommand
{
public static Command Create()
{
var fileArg = new Argument<FileInfo>("scenario-file", "Path to the scenario JSON file");
var command = new Command("dry-run", "Print event timeline without calling the API")
{
fileArg
};
command.SetHandler(async (FileInfo file) =>
{
var scenario = ScenarioLoader.Load(file.FullName);
var errors = ScenarioValidator.Validate(scenario);
if (errors.Count > 0)
{
SimulatorConsole.Error($"Scenario validation failed with {errors.Count} error(s):");
foreach (var err in errors) SimulatorConsole.Error($" • {err}");
return;
}
var engine = new ReplayEngine(client: null!, poller: null);
await engine.RunAsync(scenario, new ReplayOptions(DryRun: true));
}, fileArg);
return command;
}
}
@@ -0,0 +1,56 @@
using System.CommandLine;
public static class ReplayAllCommand
{
public static Command Create()
{
var dirArg = new Argument<DirectoryInfo>("directory", "Directory containing scenario JSON files");
var speedOpt = new Option<double>("--speed", () => 60);
var baseUrlOpt = new Option<string>("--base-url", () => "http://localhost:5270");
var command = new Command("replay-all", "Replay all scenarios in a directory")
{
dirArg, speedOpt, baseUrlOpt
};
command.SetHandler(async (DirectoryInfo dir, double speed, string baseUrl) =>
{
var files = dir.GetFiles("*.json")
.Where(f => f.Name != "schema.json")
.OrderBy(f => f.Name)
.ToList();
if (files.Count == 0)
{
SimulatorConsole.Warn($"No .json scenario files found in {dir.FullName}");
return;
}
SimulatorConsole.Header($"Replaying {files.Count} scenarios from {dir.Name}");
using var http = new HttpClient { BaseAddress = new Uri(baseUrl) };
var client = new VigilCareApiClient(http);
var engine = new ReplayEngine(client, poller: null);
var results = new List<ReplayResult>();
foreach (var file in files)
{
SimulatorConsole.Divider();
var scenario = ScenarioLoader.Load(file.FullName);
var errors = ScenarioValidator.Validate(scenario);
if (errors.Count > 0)
{
SimulatorConsole.Error($"Skipping {file.Name}: {errors.Count} validation error(s)");
continue;
}
var result = await engine.RunAsync(scenario, new ReplayOptions(speed));
results.Add(result);
}
SimulatorConsole.BatchSummary(results);
}, dirArg, speedOpt, baseUrlOpt);
return command;
}
}
@@ -0,0 +1,40 @@
using System.CommandLine;
public static class ReplayCommand
{
public static Command Create()
{
var fileArg = new Argument<FileInfo>("scenario-file");
var speedOpt = new Option<double>("--speed", () => 60, "Speed multiplier (0=instant, 1=realtime)");
var baseUrlOpt = new Option<string>("--base-url", () => "http://localhost:5270");
var pollOpt = new Option<bool>("--poll", () => false, "Poll alerts/scores after each cluster");
var pollIntervalOpt = new Option<int>("--poll-interval", () => 5, "Seconds between polls");
var command = new Command("replay", "Replay a scenario against the API")
{
fileArg, speedOpt, baseUrlOpt, pollOpt, pollIntervalOpt
};
command.SetHandler(async (FileInfo file, double speed, string baseUrl, bool poll, int pollInterval) =>
{
var scenario = ScenarioLoader.Load(file.FullName);
var errors = ScenarioValidator.Validate(scenario);
if (errors.Count > 0)
{
SimulatorConsole.Error($"Scenario validation failed with {errors.Count} error(s):");
foreach (var err in errors) SimulatorConsole.Error($" • {err}");
return;
}
using var http = new HttpClient { BaseAddress = new Uri(baseUrl) };
var client = new VigilCareApiClient(http);
var poller = poll ? new ApiPoller(client) : null;
var engine = new ReplayEngine(client, poller);
var options = new ReplayOptions(speed, poll, pollInterval);
await engine.RunAsync(scenario, options);
}, fileArg, speedOpt, baseUrlOpt, pollOpt, pollIntervalOpt);
return command;
}
}
@@ -0,0 +1,37 @@
using System.CommandLine;
public static class ValidateCommand
{
public static Command Create()
{
var fileArg = new Argument<FileInfo>("scenario-file", "Path to the scenario JSON file");
var command = new Command("validate", "Validate a scenario file without replaying")
{
fileArg
};
command.SetHandler((FileInfo file) =>
{
var scenario = ScenarioLoader.Load(file.FullName);
var errors = ScenarioValidator.Validate(scenario);
if (errors.Count == 0)
{
SimulatorConsole.Success($"✓ {file.Name} is valid");
SimulatorConsole.Info($" {scenario.Events.Count} events over " +
$"{scenario.Events.Last().OffsetMinutes} minutes");
SimulatorConsole.Info($" Patient: {scenario.Patient.FirstName} " +
$"{scenario.Patient.LastName}");
SimulatorConsole.Info($" Tags: {string.Join(", ", scenario.Scenario.Tags ?? new())}");
}
else
{
SimulatorConsole.Error($"✗ {file.Name} has {errors.Count} error(s):");
foreach (var err in errors)
SimulatorConsole.Error($" • {err}");
}
}, fileArg);
return command;
}
}