From f1623656154771489fa1437e7257b27b1b89bce1 Mon Sep 17 00:00:00 2001 From: Brennan Beam <64556723+Brennan1994@users.noreply.github.com> Date: Fri, 3 Apr 2026 09:58:34 -0700 Subject: [PATCH 1/2] bug fix in test util. produce individual and summary csv --- HEC.FDA.TestingUtility/ComputeRunner.cs | 12 + .../Reporting/CsvReportFactory.cs | 305 +++++++----------- 2 files changed, 126 insertions(+), 191 deletions(-) diff --git a/HEC.FDA.TestingUtility/ComputeRunner.cs b/HEC.FDA.TestingUtility/ComputeRunner.cs index 060fd772..6304fbf8 100644 --- a/HEC.FDA.TestingUtility/ComputeRunner.cs +++ b/HEC.FDA.TestingUtility/ComputeRunner.cs @@ -71,6 +71,7 @@ public async Task RunAsync() Stopwatch studyStopwatch = Stopwatch.StartNew(); int studyErrors = 0; int studyCompleted = 0; + CsvReportFactory studyReportFactory = new(); try { @@ -94,23 +95,27 @@ public async Task RunAsync() SaveStageDamageResults(compute.ElementName, sdResult.StageDamageFunctions); WriteStructureDetails(study.StudyId, compute.ElementName, sdResult); _csvReportFactory.AddStageDamageSummary(study.StudyId, compute.ElementName, sdResult.StageDamageFunctions); + studyReportFactory.AddStageDamageSummary(study.StudyId, compute.ElementName, sdResult.StageDamageFunctions); break; case "scenario": ScenarioResults scenarioResults = ScenarioRunner.RunScenario(compute.ElementName, _cts.Token); IASElement scenarioElement = SaveScenarioResults(compute.ElementName, scenarioResults); _csvReportFactory.AddScenarioResults(study.StudyId, scenarioElement); + studyReportFactory.AddScenarioResults(study.StudyId, scenarioElement); break; case "alternative": AlternativeResults altResults = AlternativeRunner.RunAlternative(compute.ElementName, _cts.Token); AlternativeElement altElement = SaveAlternativeResults(compute.ElementName, altResults); _csvReportFactory.AddAlternativeResults(study.StudyId, altElement); + studyReportFactory.AddAlternativeResults(study.StudyId, altElement); break; case "alternativecomparison": (AlternativeComparisonReportResults compResults, List<(int altId, string altName)> withProjAlts) = RunAlternativeComparisonWithMetadata(compute.ElementName, _cts.Token); _csvReportFactory.AddAlternativeComparisonResults(study.StudyId, compute.ElementName, compResults, withProjAlts); + studyReportFactory.AddAlternativeComparisonResults(study.StudyId, compute.ElementName, compResults, withProjAlts); break; default: @@ -149,6 +154,13 @@ public async Task RunAsync() studyTimings.Add((study.StudyId, studyStopwatch.Elapsed, studyCompleted, studyErrors)); completed += studyCompleted; errors += studyErrors; + + // Save per-study CSV report + string studyOutputDir = Path.Combine(_outputDir, study.StudyId); + Directory.CreateDirectory(studyOutputDir); + string studyCsvPath = Path.Combine(studyOutputDir, "results_report.csv"); + studyReportFactory.SaveReport(studyCsvPath); + Console.WriteLine($" Completed in {FormatDuration(studyStopwatch.Elapsed)} ({studyCompleted} succeeded, {studyErrors} failed)"); Console.WriteLine(); } diff --git a/HEC.FDA.TestingUtility/Reporting/CsvReportFactory.cs b/HEC.FDA.TestingUtility/Reporting/CsvReportFactory.cs index 167dd832..d1250d82 100644 --- a/HEC.FDA.TestingUtility/Reporting/CsvReportFactory.cs +++ b/HEC.FDA.TestingUtility/Reporting/CsvReportFactory.cs @@ -11,51 +11,140 @@ namespace HEC.FDA.TestingUtility.Reporting; /// /// Factory class that produces a comprehensive CSV report containing all tabularly /// visualized results from FDA computations across all studies. -/// Uses ViewModel row item classes for scenario and alternative results. +/// All data is extracted eagerly when Add* methods are called (while the correct +/// study is loaded in StudyCache), not deferred to SaveReport(). /// public class CsvReportFactory { - private readonly List<(string StudyId, IASElement Element)> _scenarioElements = []; - private readonly List<(string StudyId, AlternativeElement Element)> _alternativeElements = []; + private readonly StringBuilder _scenarioResults = new(); + private readonly StringBuilder _scenarioDamCat = new(); + private readonly StringBuilder _scenarioPerformance = new(); + private readonly StringBuilder _scenarioAssurance = new(); + private readonly StringBuilder _alternativeResults = new(); + private readonly StringBuilder _alternativeDamCat = new(); private readonly StringBuilder _stageDamageSummary = new(); private readonly StringBuilder _altComparisonSummary = new(); private readonly StringBuilder _altComparisonByCategory = new(); public CsvReportFactory() { - WriteStageDamageHeader(); - WriteAltComparisonHeaders(); - } - - private void WriteStageDamageHeader() - { + _scenarioResults.AppendLine("Study ID,Name,Analysis Year,Impact Area,Mean EAD,25th Percentile EAD,50th Percentile EAD,75th Percentile EAD"); + _scenarioDamCat.AppendLine("Study ID,Scenario Name,Analysis Year,Impact Area,Damage Category,Asset Category,Mean EAD"); + _scenarioPerformance.AppendLine("Study ID,Name,Analysis Year,Impact Area,Threshold Type,Threshold Value,LT Risk 10yr,LT Risk 30yr,LT Risk 50yr,Mean AEP,Median AEP,Assurance 0.10,Assurance 0.04,Assurance 0.02,Assurance 0.01,Assurance 0.004,Assurance 0.002"); + _scenarioAssurance.AppendLine("Study ID,Name,Analysis Year,Impact Area,Threshold Type,Threshold Value,Mean AEP,Median AEP,90% Assurance AEP,Assurance of 0.10 AEP,Assurance of 0.04 AEP,Assurance of 0.02 AEP,Assurance of 0.01 AEP,Assurance of 0.004 AEP,Assurance of 0.002 AEP"); + _alternativeResults.AppendLine("Study ID,Name,Impact Area,Base Year,Future Year,Discount Rate,Period of Analysis,Mean EqAD,25th Percentile EqAD,50th Percentile EqAD,75th Percentile EqAD"); + _alternativeDamCat.AppendLine("Study ID,Scenario Name,Impact Area,Damage Category,Asset Category,Mean EqAD"); _stageDamageSummary.AppendLine("Study ID,Element Name,Impact Area ID,Impact Area Name,Damage Category,Asset Category,Point Count,Min Stage,Max Stage, Median Sample Integral"); - } - - private void WriteAltComparisonHeaders() - { _altComparisonSummary.AppendLine("Study ID,Report Name,With Project Alt,Impact Area ID,Without Proj EqAD,With Proj EqAD,EqAD Reduced,EqAD Reduced 25th Pct,EqAD Reduced 50th Pct,EqAD Reduced 75th Pct,Without Proj Base EAD,With Proj Base EAD,Base EAD Reduced,Without Proj Future EAD,With Proj Future EAD,Future EAD Reduced"); _altComparisonByCategory.AppendLine("Study ID,Report Name,With Project Alt,Impact Area ID,Damage Category,Asset Category,EqAD Reduced,Base Year EAD Reduced,Future Year EAD Reduced"); } /// - /// Adds a scenario element to be included in the report. - /// Uses ScenarioDamageRowItem, ScenarioDamCatRowItem, and ScenarioPerformanceRowItem for data extraction. + /// Extracts scenario results immediately while the correct study is loaded in StudyCache. /// public void AddScenarioResults(string studyId, IASElement element) { if (element?.Results == null) return; - _scenarioElements.Add((studyId, element)); + + try + { + var rows = ScenarioDamageRowItem.CreateScenarioDamageRowItems(element); + foreach (var row in rows) + { + _scenarioResults.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.AnalysisYear)},{EscapeCsv(row.ImpactArea)},{row.Mean:F2},{row.Point25:F2},{row.Point5:F2},{row.Point75:F2}"); + } + } + catch (Exception ex) + { + Console.WriteLine($" Warning: Error extracting scenario results for {element.Name}: {ex.Message}"); + } + + try + { + var rows = ScenarioDamCatRowItem.CreateScenarioDamCatRowItems(element); + foreach (var row in rows) + { + _scenarioDamCat.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.AnalysisYear)},{EscapeCsv(row.ImpactAreaName)},{EscapeCsv(row.DamCat)},{EscapeCsv(row.AssetCat)},{row.MeanDamage:F2}"); + } + } + catch (Exception ex) + { + Console.WriteLine($" Warning: Error extracting scenario damage categories for {element.Name}: {ex.Message}"); + } + + try + { + foreach (var iaResult in element.Results.ResultsList) + { + int iasID = iaResult.ImpactAreaID; + SpecificIAS? ias = element.SpecificIASElements.FirstOrDefault(s => s.ImpactAreaID == iasID); + if (ias == null) continue; + + foreach (var threshold in iaResult.PerformanceByThresholds.ListOfThresholds) + { + var row = new ScenarioPerformanceRowItem(element, ias, threshold); + _scenarioPerformance.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.AnalysisYear)},{EscapeCsv(row.ImpactArea)},{EscapeCsv(row.ThresholdType)},{row.ThresholdValue:F2},{row.LongTerm10:F4},{row.LongTerm30:F4},{row.LongTerm50:F4},{row.Mean:F6},{row.Median:F6},{row.Threshold1:F4},{row.Threshold04:F4},{row.Threshold02:F4},{row.Threshold01:F4},{row.Threshold004:F4},{row.Threshold002:F4}"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($" Warning: Error extracting scenario performance for {element.Name}: {ex.Message}"); + } + + try + { + foreach (var iaResult in element.Results.ResultsList) + { + int iasID = iaResult.ImpactAreaID; + SpecificIAS? ias = element.SpecificIASElements.FirstOrDefault(s => s.ImpactAreaID == iasID); + if (ias == null) continue; + + foreach (var threshold in iaResult.PerformanceByThresholds.ListOfThresholds) + { + var row = new AssuranceOfAEPRowItem(element, ias, threshold); + _scenarioAssurance.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.AnalysisYear)},{EscapeCsv(row.ImpactArea)},{EscapeCsv(row.ThresholdType)},{row.ThresholdValue:F2},{row.Mean:F6},{row.Median:F6},{row.NinetyPercentAssurance:F6},{row.AEP1:F4},{row.AEP04:F4},{row.AEP02:F4},{row.AEP01:F4},{row.AEP004:F4},{row.AEP002:F4}"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($" Warning: Error extracting scenario assurance for {element.Name}: {ex.Message}"); + } } /// - /// Adds an alternative element to be included in the report. - /// Uses AlternativeDamageRowItem and AlternativeDamCatRowItem for data extraction. + /// Extracts alternative results immediately while the correct study is loaded in StudyCache. /// public void AddAlternativeResults(string studyId, AlternativeElement element) { if (element?.Results == null || element.Results.IsNull) return; - _alternativeElements.Add((studyId, element)); + + try + { + var rows = AlternativeDamageRowItem.CreateAlternativeDamageRowItems(element); + foreach (var row in rows) + { + _alternativeResults.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.ImpactArea)},{row.BaseYear},{row.FutureYear},{row.DiscountRate:F4},{row.PeriodOfAnalysis},{row.Mean:F2},{row.Point75:F2},{row.Point5:F2},{row.Point25:F2}"); + } + } + catch (Exception ex) + { + Console.WriteLine($" Warning: Error extracting alternative results for {element.Name}: {ex.Message}"); + } + + try + { + var rows = AlternativeDamCatRowItem.CreateAlternativeDamCatRowItems(element); + foreach (var row in rows) + { + _alternativeDamCat.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.ImpactAreaName)},{EscapeCsv(row.DamCat)},{EscapeCsv(row.AssetCat)},{row.MeanDamage:F2}"); + } + } + catch (Exception ex) + { + Console.WriteLine($" Warning: Error extracting alternative damage categories for {element.Name}: {ex.Message}"); + } } /// @@ -161,27 +250,27 @@ public void SaveReport(string outputPath) report.AppendLine(); report.AppendLine("=== SCENARIO RESULTS ==="); - report.Append(BuildScenarioResultsSection()); + report.Append(_scenarioResults); report.AppendLine(); report.AppendLine("=== SCENARIO DAMAGE BY CATEGORY ==="); - report.Append(BuildScenarioDamCatSection()); + report.Append(_scenarioDamCat); report.AppendLine(); report.AppendLine("=== SCENARIO PERFORMANCE ==="); - report.Append(BuildScenarioPerformanceSection()); + report.Append(_scenarioPerformance); report.AppendLine(); report.AppendLine("=== SCENARIO ASSURANCE OF AEP ==="); - report.Append(BuildScenarioAssuranceSection()); + report.Append(_scenarioAssurance); report.AppendLine(); report.AppendLine("=== ALTERNATIVE RESULTS ==="); - report.Append(BuildAlternativeResultsSection()); + report.Append(_alternativeResults); report.AppendLine(); report.AppendLine("=== ALTERNATIVE DAMAGE BY CATEGORY ==="); - report.Append(BuildAlternativeDamCatSection()); + report.Append(_alternativeDamCat); report.AppendLine(); report.AppendLine("=== STAGE DAMAGE SUMMARY ==="); @@ -199,172 +288,6 @@ public void SaveReport(string outputPath) Console.WriteLine($"CSV report saved to: {outputPath}"); } - #region Scenario Section Builders - - private StringBuilder BuildScenarioResultsSection() - { - StringBuilder sb = new(); - sb.AppendLine("Study ID,Name,Analysis Year,Impact Area,Mean EAD,25th Percentile EAD,50th Percentile EAD,75th Percentile EAD"); - - foreach (var (studyId, element) in _scenarioElements) - { - try - { - var rows = ScenarioDamageRowItem.CreateScenarioDamageRowItems(element); - foreach (var row in rows) - { - sb.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.AnalysisYear)},{EscapeCsv(row.ImpactArea)},{row.Mean:F2},{row.Point25:F2},{row.Point5:F2},{row.Point75:F2}"); - } - } - catch (Exception ex) - { - Console.WriteLine($" Warning: Error extracting scenario results for {element.Name}: {ex.Message}"); - } - } - - return sb; - } - - private StringBuilder BuildScenarioDamCatSection() - { - StringBuilder sb = new(); - sb.AppendLine("Study ID,Scenario Name,Analysis Year,Impact Area,Damage Category,Asset Category,Mean EAD"); - - foreach (var (studyId, element) in _scenarioElements) - { - try - { - var rows = ScenarioDamCatRowItem.CreateScenarioDamCatRowItems(element); - foreach (var row in rows) - { - sb.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.AnalysisYear)},{EscapeCsv(row.ImpactAreaName)},{EscapeCsv(row.DamCat)},{EscapeCsv(row.AssetCat)},{row.MeanDamage:F2}"); - } - } - catch (Exception ex) - { - Console.WriteLine($" Warning: Error extracting scenario damage categories for {element.Name}: {ex.Message}"); - } - } - - return sb; - } - - private StringBuilder BuildScenarioPerformanceSection() - { - StringBuilder sb = new(); - sb.AppendLine("Study ID,Name,Analysis Year,Impact Area,Threshold Type,Threshold Value,LT Risk 10yr,LT Risk 30yr,LT Risk 50yr,Mean AEP,Median AEP,Assurance 0.10,Assurance 0.04,Assurance 0.02,Assurance 0.01,Assurance 0.004,Assurance 0.002"); - - foreach (var (studyId, element) in _scenarioElements) - { - try - { - foreach (var iaResult in element.Results.ResultsList) - { - int iasID = iaResult.ImpactAreaID; - SpecificIAS? ias = element.SpecificIASElements.FirstOrDefault(s => s.ImpactAreaID == iasID); - if (ias == null) continue; - - foreach (var threshold in iaResult.PerformanceByThresholds.ListOfThresholds) - { - var row = new ScenarioPerformanceRowItem(element, ias, threshold); - sb.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.AnalysisYear)},{EscapeCsv(row.ImpactArea)},{EscapeCsv(row.ThresholdType)},{row.ThresholdValue:F2},{row.LongTerm10:F4},{row.LongTerm30:F4},{row.LongTerm50:F4},{row.Mean:F6},{row.Median:F6},{row.Threshold1:F4},{row.Threshold04:F4},{row.Threshold02:F4},{row.Threshold01:F4},{row.Threshold004:F4},{row.Threshold002:F4}"); - } - } - } - catch (Exception ex) - { - Console.WriteLine($" Warning: Error extracting scenario performance for {element.Name}: {ex.Message}"); - } - } - - return sb; - } - - private StringBuilder BuildScenarioAssuranceSection() - { - StringBuilder sb = new(); - sb.AppendLine("Study ID,Name,Analysis Year,Impact Area,Threshold Type,Threshold Value,Mean AEP,Median AEP,90% Assurance AEP,Assurance of 0.10 AEP,Assurance of 0.04 AEP,Assurance of 0.02 AEP,Assurance of 0.01 AEP,Assurance of 0.004 AEP,Assurance of 0.002 AEP"); - - foreach (var (studyId, element) in _scenarioElements) - { - try - { - foreach (var iaResult in element.Results.ResultsList) - { - int iasID = iaResult.ImpactAreaID; - SpecificIAS? ias = element.SpecificIASElements.FirstOrDefault(s => s.ImpactAreaID == iasID); - if (ias == null) continue; - - foreach (var threshold in iaResult.PerformanceByThresholds.ListOfThresholds) - { - var row = new AssuranceOfAEPRowItem(element, ias, threshold); - sb.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.AnalysisYear)},{EscapeCsv(row.ImpactArea)},{EscapeCsv(row.ThresholdType)},{row.ThresholdValue:F2},{row.Mean:F6},{row.Median:F6},{row.NinetyPercentAssurance:F6},{row.AEP1:F4},{row.AEP04:F4},{row.AEP02:F4},{row.AEP01:F4},{row.AEP004:F4},{row.AEP002:F4}"); - } - } - } - catch (Exception ex) - { - Console.WriteLine($" Warning: Error extracting scenario assurance for {element.Name}: {ex.Message}"); - } - } - - return sb; - } - - #endregion - - #region Alternative Section Builders - - private StringBuilder BuildAlternativeResultsSection() - { - StringBuilder sb = new(); - sb.AppendLine("Study ID,Name,Impact Area,Base Year,Future Year,Discount Rate,Period of Analysis,Mean EqAD,25th Percentile EqAD,50th Percentile EqAD,75th Percentile EqAD"); - - foreach (var (studyId, element) in _alternativeElements) - { - try - { - var rows = AlternativeDamageRowItem.CreateAlternativeDamageRowItems(element); - foreach (var row in rows) - { - sb.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.ImpactArea)},{row.BaseYear},{row.FutureYear},{row.DiscountRate:F4},{row.PeriodOfAnalysis},{row.Mean:F2},{row.Point75:F2},{row.Point5:F2},{row.Point25:F2}"); - } - } - catch (Exception ex) - { - Console.WriteLine($" Warning: Error extracting alternative results for {element.Name}: {ex.Message}"); - } - } - - return sb; - } - - private StringBuilder BuildAlternativeDamCatSection() - { - StringBuilder sb = new(); - sb.AppendLine("Study ID,Scenario Name,Impact Area,Damage Category,Asset Category,Mean EqAD"); - - foreach (var (studyId, element) in _alternativeElements) - { - try - { - var rows = AlternativeDamCatRowItem.CreateAlternativeDamCatRowItems(element); - foreach (var row in rows) - { - sb.AppendLine($"{EscapeCsv(studyId)},{EscapeCsv(row.Name)},{EscapeCsv(row.ImpactAreaName)},{EscapeCsv(row.DamCat)},{EscapeCsv(row.AssetCat)},{row.MeanDamage:F2}"); - } - } - catch (Exception ex) - { - Console.WriteLine($" Warning: Error extracting alternative damage categories for {element.Name}: {ex.Message}"); - } - } - - return sb; - } - - #endregion - /// /// Escapes a value for CSV output (handles commas and quotes). /// From dfc9166329951edf17b5663bd4d2ba96fdee4205 Mon Sep 17 00:00:00 2001 From: Brennan Beam <64556723+Brennan1994@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:18:42 -0700 Subject: [PATCH 2/2] docs update --- HEC.FDA.TestingUtility/README.md | 85 +++++++++++++++--- HEC.FDA.TestingUtility/all_case_studies.json | 90 ++++++++++++++++++++ 2 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 HEC.FDA.TestingUtility/all_case_studies.json diff --git a/HEC.FDA.TestingUtility/README.md b/HEC.FDA.TestingUtility/README.md index 64a81bbf..97e2e8a2 100644 --- a/HEC.FDA.TestingUtility/README.md +++ b/HEC.FDA.TestingUtility/README.md @@ -1,6 +1,6 @@ # HEC.FDA.TestingUtility -A command-line regression testing tool for HEC-FDA studies. It loads FDA study databases, runs computations (stage damage, scenarios, alternatives, and alternative comparisons), and produces a comprehensive CSV report of the results. +A command-line regression testing tool for HEC-FDA studies. It loads FDA study databases, runs computations (stage damage, scenarios, alternatives, and alternative comparisons), and produces comprehensive CSV reports of the results. ## Purpose @@ -18,7 +18,17 @@ This utility runs FDA computations outside the GUI so that results can be valida dotnet build HEC.FDA.TestingUtility/HEC.FDA.TestingUtility.csproj ``` -### 2. Create a configuration file +### 2a. Run directly against a study file (simplest) + +If you just want to run all computations on a single study, point directly at its `.sqlite` file: + +```bash +dotnet run --project HEC.FDA.TestingUtility -- compute -sp "C:/path/to/my/study/MyStudy.sqlite" -o results +``` + +This auto-discovers and runs all stage damage, scenario, alternative, and alternative comparison elements in the study. Output defaults to the study's directory if `-o` is not specified. + +### 2b. Run with a configuration file (multiple studies or selective runs) Copy `example-config.json` from this project directory and update `networkSourcePath` to point to your FDA study folder (the folder containing the `.sqlite` or `.db` file): @@ -82,11 +92,32 @@ dotnet run --project HEC.FDA.TestingUtility -- compute -c my-test.json -o result # Filter to multiple studies dotnet run --project HEC.FDA.TestingUtility -- compute -c my-test.json -s "study-a" -s "study-b" + +# Run directly against a single .sqlite file +dotnet run --project HEC.FDA.TestingUtility -- compute -sp "C:/Studies/Muncie/Muncie.sqlite" -o results ``` ### 4. Check the output -After the run completes, look for `results_report.csv` in the output directory. It contains separate sections for scenario EAD, damage by category, performance metrics, alternative EqAD, stage damage summaries, and alternative comparison results. +After the run completes, the output directory will contain: + +``` +results/ + results_report.csv # Global summary across all studies + muncie/ + results_report.csv # Per-study report for Muncie + StructureDetails/ + My Stage Damage_StructureStageDamageDetails.csv + My Stage Damage_DamagedElementCountsByStage.csv + greenbrook/ + results_report.csv # Per-study report for Greenbrook + StructureDetails/ + ... +``` + +- **`results_report.csv`** (root) - Combined report across all studies with sections for scenario EAD, damage by category, performance metrics, alternative EqAD, stage damage summaries, and alternative comparison results. +- **`{studyId}/results_report.csv`** - Same report format, but scoped to a single study. +- **`{studyId}/StructureDetails/`** - Generated for computed (non-manual) stage damage elements. Contains per-structure stage-damage details and damaged element counts by stage. The console output will also show a summary: @@ -118,16 +149,19 @@ dotnet build HEC.FDA.TestingUtility/HEC.FDA.TestingUtility.csproj The utility uses the `System.CommandLine` library and exposes a `compute` subcommand: ```bash -dotnet run --project HEC.FDA.TestingUtility -- compute --config [options] +dotnet run --project HEC.FDA.TestingUtility -- compute [options] ``` +You must provide either `--config` or `--study-path` (but not both). + ### Options -| Option | Alias | Required | Description | -|---|---|---|---| -| `--config` | `-c` | Yes | Path to a JSON configuration file defining the studies and computations to run. | -| `--output` | `-o` | No | Output directory for generated files. Defaults to the current working directory. | -| `--study` | `-s` | No | Filter to one or more specific study IDs. Can be specified multiple times. | +| Option | Alias | Description | +|---|---|---| +| `--config` | `-c` | Path to a JSON configuration file defining the studies and computations to run. Required unless `--study-path` is used. | +| `--study-path` | `-sp` | Path to a `.sqlite` study file. Runs all computations (stage damage, scenarios, alternatives, alternative comparisons) automatically. Required unless `--config` is used. | +| `--output` | `-o` | Output directory for generated files. Defaults to the current working directory (or the study directory when using `--study-path`). | +| `--study` | `-s` | Filter to one or more specific study IDs. Can be specified multiple times. Only applies when using `--config`. | ### Examples @@ -143,6 +177,18 @@ Run only a specific study: dotnet run --project HEC.FDA.TestingUtility -- compute -c tests/regression.json -s "muncie" ``` +Run a single study directly without a config file: + +```bash +dotnet run --project HEC.FDA.TestingUtility -- compute -sp "E:/Studies/Muncie/Muncie.sqlite" -o results/ +``` + +Run all case studies from a directory (see `example-directory.json`): + +```bash +dotnet run --project HEC.FDA.TestingUtility -- compute -c example-directory.json -o results/ +``` + ## Configuration File The configuration is a JSON file with the following structure: @@ -152,7 +198,7 @@ The configuration is a JSON file with the following structure: "testSuiteId": "regression-v1", "globalSettings": { "localTempDirectory": "C:/temp/FDATests", - "timeoutMinutes": 30 + "timeoutMinutes": 120 }, "studies": [ { @@ -174,7 +220,7 @@ The configuration is a JSON file with the following structure: | Field | Default | Description | |---|---|---| | `localTempDirectory` | System temp + `FDATests` | Directory where studies are copied locally before computation. | -| `timeoutMinutes` | `30` | Maximum wall-clock time for all computations before cancellation. | +| `timeoutMinutes` | `120` | Maximum wall-clock time for all computations before cancellation. Set to `0` to disable the timeout. | ### Study Configuration @@ -211,6 +257,15 @@ Valid `type` values (case-insensitive): Computations are automatically sorted in dependency order: stage damage -> scenario -> alternative -> alternative comparison. This means you can list them in any order and the utility will execute them correctly. +## Example Configuration Files + +| File | Description | +|---|---| +| `example-config.json` | Minimal template for a single study with all `runAll*` flags enabled. | +| `example-directory.json` | Template for running all studies found as subfolders under a common parent directory. | +| `all_case_studies.json` | Real-world config targeting 9 case studies with all computations. | +| `sunnyvale_alt_only.json` | Example of a selective run (alternatives only). | + ## How It Works 1. **Study Loading** - The study folder is copied from `networkSourcePath` to a local temp directory to avoid locking network files. The SQLite database is opened and all element types are loaded into the `StudyCache` in dependency order (terrains, impact areas, hydraulics, frequencies, inventories, stage damage, scenarios, alternatives, etc.). @@ -223,7 +278,7 @@ Computations are automatically sorted in dependency order: stage damage -> scena 3. **Result Saving** - Computed results are saved back to the temp study database so that downstream computations (e.g., alternatives depending on scenario results) can access them. -4. **CSV Report** - A single `results_report.csv` file is written to the output directory containing sections for: +4. **CSV Report** - A `results_report.csv` file is written both globally (to the output directory) and per-study (to `{outputDir}/{studyId}/`). Each report contains sections for: - Scenario results (mean and percentile EAD by impact area) - Scenario damage by category - Scenario performance (long-term risk, AEP, assurance) @@ -234,7 +289,11 @@ Computations are automatically sorted in dependency order: stage damage -> scena - Alternative comparison summary (EqAD reduced, base/future EAD reduced) - Alternative comparison by damage category -5. **Cleanup** - When a study finishes, the temp copy is deleted automatically. +5. **Structure Details** - For computed (non-manual) stage damage elements, two additional CSVs are written to `{outputDir}/{studyId}/StructureDetails/`: + - `{elementName}_StructureStageDamageDetails.csv` - Per-structure stage-damage detail curves. + - `{elementName}_DamagedElementCountsByStage.csv` - Number of damaged elements at each stage. + +6. **Cleanup** - When a study finishes, the temp copy is deleted automatically. ## Project Structure diff --git a/HEC.FDA.TestingUtility/all_case_studies.json b/HEC.FDA.TestingUtility/all_case_studies.json new file mode 100644 index 00000000..67a0c743 --- /dev/null +++ b/HEC.FDA.TestingUtility/all_case_studies.json @@ -0,0 +1,90 @@ +{ + "testSuiteId": "all-case-studies", + "globalSettings": { + "localTempDirectory": "E:\\HEC-FDA_CaseStudies_Temp", + "timeoutMinutes": 360 + }, + "studies": [ + { + "studyId": "glendive", + "studyName": "Glendive", + "networkSourcePath": "E:\\HEC-FDA_CaseStudies_New\\2.0.2\\Glendive", + "runAllStageDamage": true, + "runAllScenarios": true, + "runAllAlternatives": true, + "runAllAlternativeComparisons": true + }, + { + "studyId": "greenbrook", + "studyName": "Greenbrook", + "networkSourcePath": "E:\\HEC-FDA_CaseStudies_New\\2.0.2\\Greenbrook", + "runAllStageDamage": true, + "runAllScenarios": true, + "runAllAlternatives": true, + "runAllAlternativeComparisons": true + }, + { + "studyId": "london-orleans", + "studyName": "LondonOrleans", + "networkSourcePath": "E:\\HEC-FDA_CaseStudies_New\\2.0.2\\LondonOrleans", + "runAllStageDamage": true, + "runAllScenarios": true, + "runAllAlternatives": true, + "runAllAlternativeComparisons": true + }, + { + "studyId": "muncie", + "studyName": "Muncie", + "networkSourcePath": "E:\\HEC-FDA_CaseStudies_New\\2.0.2\\Muncie", + "runAllStageDamage": true, + "runAllScenarios": true, + "runAllAlternatives": true, + "runAllAlternativeComparisons": true + }, + { + "studyId": "north-desoto", + "studyName": "NorthDeSoto", + "networkSourcePath": "E:\\HEC-FDA_CaseStudies_New\\2.0.2\\NorthDeSoto", + "runAllStageDamage": true, + "runAllScenarios": true, + "runAllAlternatives": true, + "runAllAlternativeComparisons": true + }, + { + "studyId": "river-des-peres", + "studyName": "RiverDesPeres", + "networkSourcePath": "E:\\HEC-FDA_CaseStudies_New\\2.0.2\\RiverDesPeres", + "runAllStageDamage": true, + "runAllScenarios": true, + "runAllAlternatives": true, + "runAllAlternativeComparisons": true + }, + { + "studyId": "tarfuna", + "studyName": "Tarfuna", + "networkSourcePath": "E:\\HEC-FDA_CaseStudies_New\\2.0.2\\Tarfuna", + "runAllStageDamage": true, + "runAllScenarios": true, + "runAllAlternatives": true, + "runAllAlternativeComparisons": true + }, + { + "studyId": "watertown", + "studyName": "Watertown", + "networkSourcePath": "E:\\HEC-FDA_CaseStudies_New\\2.0.2\\Watertown", + "runAllStageDamage": true, + "runAllScenarios": true, + "runAllAlternatives": true, + "runAllAlternativeComparisons": true + }, + { + "studyId": "west-sacramento", + "studyName": "WestSacramento", + "networkSourcePath": "E:\\HEC-FDA_CaseStudies_New\\2.0.2\\WestSacramento", + "runAllStageDamage": true, + "runAllScenarios": true, + "runAllAlternatives": true, + "runAllAlternativeComparisons": true + } + ] +}