Skip to main content

EPANET Input File Export Feature - Design Document

Version: 1.0 Date: October 17, 2025 Status: Design Phase


πŸ“‹ Table of Contents​

  1. Overview
  2. Goals and Requirements
  3. Architecture Design
  4. Component Specifications
  5. Implementation Plan
  6. Testing Strategy
  7. Security Considerations
  8. Performance Considerations
  9. Future Enhancements

1. Overview​

1.1 Purpose​

Enable users to download an EPANET input file (.inp) that reflects all database modifications for a given scenario without running a full simulation.

1.2 Background​

Currently, scenarios modify the base EPANET input file through database overrides (node properties, link properties, options, controls, etc.). Users cannot easily see or share the "effective" input file that would be used in a simulation. This feature allows users to:

  • Inspect the final network configuration
  • Share scenario configurations
  • Import into external EPANET tools
  • Archive scenario snapshots

1.3 User Story​

"As a hydraulic engineer, I want to download the modified EPANET input file for my scenario so that I can review the complete network configuration and share it with colleagues or use it in external analysis tools."


2. Goals and Requirements​

2.1 Functional Requirements​

Must Have (MVP)​

  • FR-1: User can download an INP file for any scenario via UI button
  • FR-2: Downloaded file includes all database modifications (properties, controls, options, patterns, curves)
  • FR-3: Downloaded file is immediately available (no queuing)
  • FR-4: Filename includes scenario name and ID for easy identification
  • FR-5: Export operation does not run simulation (fast response)

Should Have​

  • FR-6: Loading indicator during export
  • FR-7: Error messages if export fails
  • FR-8: Export works for scenarios without prior runs

Could Have (Future)​

  • FR-9: Bulk export multiple scenarios
  • FR-10: Export format options (JSON, CSV, etc.)
  • FR-11: Email download link for large files

2.2 Non-Functional Requirements​

  • NFR-1: Export completes within 30 seconds for typical networks (< 10,000 elements)
  • NFR-2: No disk space buildup (temp files cleaned up)
  • NFR-3: Concurrent exports supported (multiple users)
  • NFR-4: Handles large networks (up to 50,000 elements)
  • NFR-5: Secure file handling (no path traversal, proper permissions)

3. Architecture Design​

3.1 System Overview​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Frontend β”‚
β”‚ (React) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ HTTP GET /api/Scenario/download/{id}
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ API β”‚
β”‚ (ASP.NET) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ Process.Start("EpanetConsoleCore.exe --export-only")
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ EpanetConsole β”‚
β”‚ Core β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ 1. Load scenario from DB
β”‚ 2. Open base INP file
β”‚ 3. Apply DB synchronization
β”‚ 4. EN_saveinpfile()
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Temp File β”‚
β”‚ (exports/) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ Read & Stream to Browser
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Browser β”‚
β”‚ (Download) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3.2 Data Flow​

  1. User Action: Clicks download button in frontend
  2. API Request: GET /api/Scenario/download/{id}
  3. API Processing:
    • Validates scenario exists
    • Spawns EpanetConsoleCore process with --export-only flag
    • Captures stdout/stderr
    • Waits for process completion
  4. Console Core Processing:
    • Loads scenario from database
    • Opens base INP file with EPANET wrapper
    • Applies all database modifications via NetworkSynchronizationService
    • Saves modified network to temp file using EN_saveinpfile()
    • Outputs file path to stdout: EXPORT_SUCCESS:<file-path>
  5. API Response:
    • Reads temp file into memory
    • Deletes temp file
    • Streams file content to browser with proper headers
  6. Browser: Downloads file with sanitized filename

3.3 Component Diagram​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Frontend β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ ScenariosTableβ”‚ β”‚ScenarioDetails β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ Dialog β”‚ β”‚
β”‚ β”‚ [Download] β”‚ β”‚ [Download] β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ API Layer β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ ScenarioController.cs β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ DownloadScenarioInputFile(int id) β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Validate scenario β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Start export process β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Stream file to response β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ EpanetConsoleCore β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Program.cs β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ Main(args[]) β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Parse --export-only flag β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Route to ExportScenarioInputFile() β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ ExportScenarioInputFile(connStr, scenarioId) β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Load scenario from DB β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Initialize EpanetWrapper β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Run NetworkSynchronizationService β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Call EN_saveinpfile() β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ - Output file path to stdout β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ NetworkSynchronizationService.cs β”‚ β”‚
β”‚ β”‚ - SynchronizeFromDatabase() β”‚ β”‚
β”‚ β”‚ (Already exists - reuse) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ EpanetWrapper.cs β”‚ β”‚
β”‚ β”‚ - EN_saveinpfile(string outputPath) β”‚ β”‚
β”‚ β”‚ (Already exists - reuse) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ File System β”‚
β”‚ <epanetconsolecore-dir>/exports/ β”‚
β”‚ scenario_5_20251017_143522.inp β”‚
β”‚ (Temporary files - cleaned after download) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

4. Component Specifications​

4.1 Frontend Components​

4.1.1 ScenariosTable.jsx​

New Function: handleDownloadScenario

const handleDownloadScenario = async (scenario) => {
try {
setDownloadingScenarioId(scenario.id); // Show loading state

const response = await fetch(
`${API_BASE_URL}/api/Scenario/download/${scenario.id}`,
{
method: 'GET',
headers: {
'Accept': 'text/plain'
}
}
);

if (!response.ok) {
const error = await response.json();
throw new Error(error.Message || 'Download failed');
}

const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = response.headers.get('Content-Disposition')
?.split('filename=')[1]
?.replace(/"/g, '')
|| `scenario_${scenario.id}.inp`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);

// Show success notification (if notification system exists)
} catch (error) {
console.error('Error downloading scenario:', error);
// Show error notification to user
alert(`Failed to download scenario: ${error.message}`);
} finally {
setDownloadingScenarioId(null);
}
};

UI Changes:

  • Add Download icon button to actions column
  • Show CircularProgress on button during download
  • Disable button during download

4.1.2 ScenarioDetailsDialog.jsx​

UI Changes:

  • Add "Download INP File" button to dialog actions
  • Position near Edit/Delete buttons
  • Use same handler as ScenariosTable

4.2 API Component​

4.2.1 ScenarioController.cs​

New Endpoint: DownloadScenarioInputFile

Route: GET /api/Scenario/download/{id}

Request:

  • Path Parameter: id (int) - Scenario ID

Response:

  • Success (200): File stream with headers
    • Content-Type: text/plain
    • Content-Disposition: attachment; filename="<scenario-name>_scenario_<id>.inp"
  • Not Found (404): Scenario doesn't exist
  • Internal Server Error (500): Export failed

Implementation Details:

[HttpGet("download/{id}")]
public async Task<IActionResult> DownloadScenarioInputFile(int id)
{
// 1. Validate scenario exists
// 2. Configure process to call EpanetConsoleCore with --export-only
// 3. Capture stdout looking for EXPORT_SUCCESS: or EXPORT_ERROR:
// 4. Parse output file path
// 5. Read file bytes
// 6. Delete temp file
// 7. Return File() with proper headers
}

Error Handling:

  • Scenario not found β†’ 404
  • Process timeout (30s) β†’ 500 with timeout message
  • Process exit code != 0 β†’ 500 with stderr
  • File not found after success β†’ 500 (shouldn't happen)
  • EPANET error β†’ 500 with error code/message

Logging:

  • Info: Export started for scenario {id}
  • Info: Export completed successfully for scenario {id}, size: {bytes}
  • Error: Export failed for scenario {id}: {error}
  • Warning: Temp file cleanup failed: {path}

4.3 EpanetConsoleCore Component​

4.3.1 Program.cs - Argument Parsing​

Current Arguments:

args[0] = connection string
args[1] = scenario ID

New Arguments:

args[0] = connection string
args[1] = scenario ID
args[2] = --export-only (optional flag)

Parsing Logic:

bool exportOnly = false;
if (args.Length >= 3)
{
exportOnly = args[2] == "--export-only" || args[2] == "--export";
}

if (exportOnly)
{
await ExportScenarioInputFile(pgConnStr, scenarioId);
return;
}
else
{
// Existing run logic
await dwdRun(pgConnStr);
}

4.3.2 New Method: ExportScenarioInputFile​

Signature:

private static async Task ExportScenarioInputFile(string pgConnStr, int scenarioId)

Responsibilities:

  1. Initialize database context
  2. Load scenario with Options
  3. Validate scenario has InputFile
  4. Initialize EpanetWrapper with base INP file
  5. Call Startup() to open EPANET project
  6. Apply database synchronization
  7. Generate output file path
  8. Call EN_saveinpfile()
  9. Output result to stdout
  10. Clean up resources

Output Format:

  • Success: EXPORT_SUCCESS:<full-path-to-file>
  • Error: EXPORT_ERROR:<error-code>:<error-message>

File Naming Convention:

<epanetconsolecore-dir>/exports/scenario_{scenarioId}_{timestamp}.inp

Example:
C:\epanetconsolecore\exports\scenario_42_20251017_143522.inp

Error Handling:

  • Scenario not found β†’ Output error, exit code 1
  • Base INP file missing β†’ Output error, exit code 2
  • EPANET open failed β†’ Output error, exit code 3
  • Synchronization failed β†’ Output error, exit code 4
  • Save failed β†’ Output error, exit code 5

4.3.3 Reused Components​

NetworkSynchronizationService:

  • Already exists and handles all DBβ†’EPANET synchronization
  • Reuse SynchronizeFromDatabase(int scenarioId) method
  • Applies: Options, NodeProperties, LinkProperties, Patterns, Curves, Controls

EpanetWrapper:

  • Already has EN_saveinpfile(string filename) wrapper
  • Returns EPANET error code (0 = success)

5. Implementation Plan​

Phase 1: Backend Core (EpanetConsoleCore)​

Estimated Time: 3-4 hours

Tasks:​

  1. βœ… Task 1.1: Add command-line argument parsing for --export-only

    • Modify Main() in Program.cs
    • Add conditional routing
  2. βœ… Task 1.2: Implement ExportScenarioInputFile() method

    • Database initialization
    • Scenario loading and validation
    • EpanetWrapper initialization
    • NetworkSynchronizationService invocation
    • File path generation
    • EN_saveinpfile() call
    • Stdout output formatting
  3. βœ… Task 1.3: Create exports directory management

    • Ensure directory exists at startup
    • Add to .gitignore
  4. βœ… Task 1.4: Add comprehensive error handling

    • Try-catch blocks
    • Specific error codes
    • Error message formatting
  5. βœ… Task 1.5: Add logging

    • Export start/complete messages
    • Error logging
    • File size logging

Testing:

  • Unit test: Argument parsing
  • Integration test: Export for existing scenario
  • Integration test: Export for scenario without prior runs
  • Error test: Non-existent scenario
  • Error test: Missing base INP file

Phase 2: API Layer​

Estimated Time: 2-3 hours

Tasks:​

  1. βœ… Task 2.1: Add DownloadScenarioInputFile endpoint to ScenarioController

    • Route definition
    • Parameter validation
    • Process spawning logic
    • Stdout/stderr capture
  2. βœ… Task 2.2: Implement file streaming response

    • Read file bytes
    • Set proper headers (Content-Type, Content-Disposition)
    • Filename sanitization
    • Stream to response
  3. βœ… Task 2.3: Add temp file cleanup

    • Delete file after reading
    • Handle cleanup errors gracefully
    • Log cleanup failures
  4. βœ… Task 2.4: Add timeout handling

    • 30-second timeout
    • Kill process if timeout
    • Return appropriate error
  5. βœ… Task 2.5: Add logging and error responses

    • Log export requests
    • Log successes/failures
    • Structured error responses

Testing:

  • API test: Successful download
  • API test: Non-existent scenario (404)
  • API test: Process timeout (500)
  • API test: Concurrent downloads
  • API test: Large network export

Phase 3: Frontend​

Estimated Time: 2-3 hours

Tasks:​

  1. βœ… Task 3.1: Add download handler to ScenariosTable

    • Fetch API call
    • Blob handling
    • Programmatic download
    • Error handling
  2. βœ… Task 3.2: Add Download button to ScenariosTable actions column

    • Import DownloadIcon
    • Add IconButton with tooltip
    • Wire up handler
    • Add loading state
  3. βœ… Task 3.3: Add Download button to ScenarioDetailsDialog

    • Add button to actions section
    • Wire up same handler
    • Add loading state
  4. βœ… Task 3.4: Add loading indicators

    • CircularProgress on button during download
    • Disable button during download
    • Track downloading scenario ID in state
  5. βœ… Task 3.5: Add user feedback

    • Success: (Optional) Snackbar notification
    • Error: Alert or Snackbar with error message
    • Loading: Visual indication on button

Testing:

  • UI test: Click download from table
  • UI test: Click download from dialog
  • UI test: Download completes successfully
  • UI test: Error message displays
  • UI test: Loading state during download

Phase 4: Documentation and Cleanup​

Estimated Time: 1 hour

Tasks:​

  1. βœ… Task 4.1: Add .gitignore entry for exports directory
  2. βœ… Task 4.2: Update API documentation
  3. βœ… Task 4.3: Add user documentation (if applicable)
  4. βœ… Task 4.4: Code review and cleanup
  5. βœ… Task 4.5: Update CHANGELOG

6. Testing Strategy​

6.1 Unit Tests​

EpanetConsoleCore:

  • βœ… Command-line argument parsing
  • βœ… File path generation
  • βœ… Error code mapping

API:

  • βœ… Filename sanitization
  • βœ… Process output parsing

6.2 Integration Tests​

End-to-End Export Flow:

  1. Create test scenario in database
  2. Call export endpoint
  3. Verify file downloaded
  4. Verify file content is valid EPANET INP
  5. Verify temp file cleaned up

Database Synchronization Verification:

  1. Create scenario with modified properties
  2. Export INP file
  3. Parse exported file
  4. Verify modifications present in exported file

6.3 Manual Testing Checklist​

  • Download scenario with no modifications
  • Download scenario with node property overrides
  • Download scenario with link property overrides
  • Download scenario with custom options
  • Download scenario with simple controls
  • Download scenario with rule-based controls
  • Download scenario with custom patterns
  • Download scenario with custom curves
  • Download scenario that has never been run
  • Download scenario that has been run before
  • Concurrent downloads (2+ users)
  • Download large network (10,000+ elements)
  • Download with special characters in scenario name
  • Download non-existent scenario (should fail gracefully)
  • Download scenario with missing base INP (should fail gracefully)

6.4 Performance Tests​

  • Export time for small network (< 100 elements): < 3 seconds
  • Export time for medium network (1,000 elements): < 10 seconds
  • Export time for large network (10,000 elements): < 30 seconds
  • Concurrent exports (5 simultaneous): All complete successfully
  • Memory usage: No leaks after 100 exports

7. Security Considerations​

7.1 Input Validation​

βœ… Scenario ID:

  • Must be positive integer
  • Must exist in database
  • Use parameterized queries

βœ… File Paths:

  • No path traversal (../../)
  • Output only to exports directory
  • Validate directory creation

βœ… Filename Sanitization:

  • Remove/replace special characters
  • Prevent command injection
  • Limit filename length

7.2 Authorization​

Current State: No authentication/authorization in current implementation

Recommendations for Future:

  • Add user authentication
  • Verify user has access to scenario
  • Add audit logging for downloads

7.3 Resource Protection​

βœ… Disk Space:

  • Immediate temp file cleanup
  • Add disk space check before export (optional)

βœ… Process Management:

  • Timeout after 30 seconds
  • Kill runaway processes
  • Limit concurrent exports per user (optional)

βœ… Memory:

  • Stream file response (don't load entire file)
  • Clean up resources in finally blocks

7.4 Error Information Disclosure​

⚠️ Avoid exposing:

  • Full file paths in error messages
  • Database connection strings
  • Internal error details to frontend

βœ… Return generic errors to frontend, log details server-side


8. Performance Considerations​

8.1 Expected Performance​

Small Network (< 100 elements):

  • Export time: 1-3 seconds
  • File size: < 100 KB

Medium Network (1,000 elements):

  • Export time: 5-10 seconds
  • File size: 1-5 MB

Large Network (10,000 elements):

  • Export time: 15-30 seconds
  • File size: 10-50 MB

8.2 Optimization Opportunities​

Phase 1 (MVP):

  • Use synchronous file I/O (simple)
  • Single-threaded export
  • Immediate cleanup

Phase 2 (Future):

  • Asynchronous export with job queue
  • Progress reporting via WebSocket/SignalR
  • Caching for unchanged scenarios
  • Memory stream instead of temp files

8.3 Scalability​

Current Design:

  • βœ… Supports concurrent exports (separate processes)
  • βœ… No database locking (read-only operations)
  • ⚠️ Disk I/O may be bottleneck at scale

Future Improvements:

  • Background job queue (Hangfire, Azure Queue)
  • Distributed file storage (Azure Blob Storage)
  • Cached exports with TTL
  • Export notification system

9. Future Enhancements​

9.1 Short Term (Next Release)​

Export Options:

  • Include/exclude specific modifications
  • Export format: Original vs Simplified
  • Include metadata comment header

User Experience:

  • Progress bar for large exports
  • Export history (list of past exports)
  • Email notification when export completes

9.2 Medium Term (Future Releases)​

Bulk Operations:

  • Export multiple scenarios at once
  • Export all scenarios in a project
  • Zip multiple exports

Export Formats:

  • JSON format (for API consumers)
  • CSV format (for data analysis)
  • Excel format (for reporting)

Comparison Tools:

  • Diff view between scenarios
  • Export comparison report
  • Visual diff of network changes

9.3 Long Term (Future Consideration)​

Advanced Features:

  • Export to EPANET-MSX format
  • Export subnetworks (selected elements only)
  • Export timeseries data with INP
  • Integration with version control systems

Cloud Integration:

  • Export to Azure Blob Storage
  • Export to AWS S3
  • Share export via shareable link

10. Risk Assessment​

10.1 Technical Risks​

RiskImpactProbabilityMitigation
EPANET save function fails for large networksHighLowAdd timeout, test with large networks
Temp file cleanup fails, disk fills upHighLowAdd scheduled cleanup job, monitor disk space
Concurrent exports cause file conflictsMediumMediumUse unique timestamps in filenames
Export hangs indefinitelyMediumLowAdd 30s timeout, kill process
Memory leak from file streamsMediumLowUse using statements, proper disposal

10.2 Mitigation Strategies​

βœ… Timeout Protection: 30-second timeout on process βœ… File Naming: Timestamp + GUID for uniqueness βœ… Cleanup: Immediate deletion after download βœ… Resource Management: using statements for all disposables βœ… Error Handling: Try-catch at every layer βœ… Logging: Comprehensive logging for debugging


11. Success Criteria​

11.1 MVP Success Criteria​

  • User can download INP file from scenarios table
  • User can download INP file from scenario details dialog
  • Downloaded file contains all database modifications
  • Export completes in < 30 seconds for typical network
  • No temp files left on disk after download
  • Error messages are clear and actionable
  • Feature works for scenarios without prior runs

11.2 Quality Criteria​

  • Code review completed
  • All unit tests passing
  • All integration tests passing
  • Manual testing checklist completed
  • No memory leaks detected
  • Performance meets requirements
  • Documentation updated

12. Rollout Plan​

12.1 Development Environment​

  1. Implement Phase 1-4
  2. Internal testing
  3. Fix issues

12.2 Testing/Staging Environment​

  1. Deploy to staging
  2. QA testing
  3. User acceptance testing (if applicable)
  4. Performance testing

12.3 Production Environment​

  1. Deploy during maintenance window
  2. Monitor logs for errors
  3. Monitor disk space in exports directory
  4. Gather user feedback

12.4 Rollback Plan​

  • If critical issues found, remove download button from frontend
  • API endpoint can remain (no harm if not called)
  • No database changes, so no migration rollback needed

13. Appendix​

13.1 EPANET API Reference​

EN_saveinpfile():

int EN_saveinpfile(EN_Project ph, const char *filename)

Saves the network to an EPANET input file.

Returns:

  • 0 = Success
  • Error codes: See EPANET Toolkit documentation

Behavior:

  • Saves current state of network in memory
  • Includes all modifications made via API
  • Overwrites existing file if present

13.2 File Format​

EPANET INP File Structure:

[TITLE]
[JUNCTIONS]
[RESERVOIRS]
[TANKS]
[PIPES]
[PUMPS]
[VALVES]
[TAGS]
[DEMANDS]
[STATUS]
[PATTERNS]
[CURVES]
[CONTROLS]
[RULES]
[ENERGY]
[EMITTERS]
[QUALITY]
[SOURCES]
[REACTIONS]
[MIXING]
[TIMES]
[REPORT]
[OPTIONS]
[COORDINATES]
[VERTICES]
[LABELS]
[BACKDROP]
[END]

13.3 References​


Document History​

VersionDateAuthorChanges
1.02025-10-17AI AssistantInitial design document

End of Document