EPANET Input File Export Feature - Design Document
Version: 1.0 Date: October 17, 2025 Status: Design Phase
π Table of Contentsβ
- Overview
- Goals and Requirements
- Architecture Design
- Component Specifications
- Implementation Plan
- Testing Strategy
- Security Considerations
- Performance Considerations
- 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β
- User Action: Clicks download button in frontend
- API Request:
GET /api/Scenario/download/{id} - API Processing:
- Validates scenario exists
- Spawns EpanetConsoleCore process with
--export-onlyflag - Captures stdout/stderr
- Waits for process completion
- 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>
- API Response:
- Reads temp file into memory
- Deletes temp file
- Streams file content to browser with proper headers
- 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"
- Content-Type:
- 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:
- Initialize database context
- Load scenario with Options
- Validate scenario has InputFile
- Initialize EpanetWrapper with base INP file
- Call Startup() to open EPANET project
- Apply database synchronization
- Generate output file path
- Call EN_saveinpfile()
- Output result to stdout
- 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:β
β Task 1.1: Add command-line argument parsing for
--export-only- Modify
Main()in Program.cs - Add conditional routing
- Modify
β 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
β Task 1.3: Create exports directory management
- Ensure directory exists at startup
- Add to .gitignore
β Task 1.4: Add comprehensive error handling
- Try-catch blocks
- Specific error codes
- Error message formatting
β 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:β
β Task 2.1: Add
DownloadScenarioInputFileendpoint to ScenarioController- Route definition
- Parameter validation
- Process spawning logic
- Stdout/stderr capture
β Task 2.2: Implement file streaming response
- Read file bytes
- Set proper headers (Content-Type, Content-Disposition)
- Filename sanitization
- Stream to response
β Task 2.3: Add temp file cleanup
- Delete file after reading
- Handle cleanup errors gracefully
- Log cleanup failures
β Task 2.4: Add timeout handling
- 30-second timeout
- Kill process if timeout
- Return appropriate error
β 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:β
β Task 3.1: Add download handler to ScenariosTable
- Fetch API call
- Blob handling
- Programmatic download
- Error handling
β Task 3.2: Add Download button to ScenariosTable actions column
- Import DownloadIcon
- Add IconButton with tooltip
- Wire up handler
- Add loading state
β Task 3.3: Add Download button to ScenarioDetailsDialog
- Add button to actions section
- Wire up same handler
- Add loading state
β Task 3.4: Add loading indicators
- CircularProgress on button during download
- Disable button during download
- Track downloading scenario ID in state
β 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:β
- β Task 4.1: Add .gitignore entry for exports directory
- β Task 4.2: Update API documentation
- β Task 4.3: Add user documentation (if applicable)
- β Task 4.4: Code review and cleanup
- β 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:
- Create test scenario in database
- Call export endpoint
- Verify file downloaded
- Verify file content is valid EPANET INP
- Verify temp file cleaned up
Database Synchronization Verification:
- Create scenario with modified properties
- Export INP file
- Parse exported file
- 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β
| Risk | Impact | Probability | Mitigation |
|---|---|---|---|
| EPANET save function fails for large networks | High | Low | Add timeout, test with large networks |
| Temp file cleanup fails, disk fills up | High | Low | Add scheduled cleanup job, monitor disk space |
| Concurrent exports cause file conflicts | Medium | Medium | Use unique timestamps in filenames |
| Export hangs indefinitely | Medium | Low | Add 30s timeout, kill process |
| Memory leak from file streams | Medium | Low | Use 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β
- Implement Phase 1-4
- Internal testing
- Fix issues
12.2 Testing/Staging Environmentβ
- Deploy to staging
- QA testing
- User acceptance testing (if applicable)
- Performance testing
12.3 Production Environmentβ
- Deploy during maintenance window
- Monitor logs for errors
- Monitor disk space in exports directory
- 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β
- EPANET Toolkit Documentation: https://github.com/OpenWaterAnalytics/EPANET
- ASP.NET File Streaming: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads
- Process Management: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process
Document Historyβ
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2025-10-17 | AI Assistant | Initial design document |
End of Document