Scenario Configuration Models Refactoring
Date: October 15, 2025 Status: Planning Complexity: Medium - requires model changes, migrations, and API updates
Overview
This document outlines a refactoring effort to eliminate duplicate fields between Scenario and related configuration models (Options, TimeParameters, Report), and to establish proper foreign key relationships that allow for reusable configuration presets.
Problem Statement
Current Issues
Field Duplication: Demand model and PDA-related fields are duplicated between two models:
Scenario.cshas:DemandModel,PressureExponent,MinimumPressure,RequiredPressureOptions.cshas:DemandModel,PressureExponent,MinimumPressure,RequiredPressure
Unclear Relationship: The current
Optionsmodel has a nullableScenarioIdFK pointing toScenario, but this creates confusion about ownership and makes reusable presets difficult.No Configuration Reuse: Users cannot easily create and reuse common configuration sets across multiple scenarios (e.g., "PDA Standard", "High Accuracy DDA", "Quick Analysis").
Models Affected
Based on code review, the following models follow similar patterns:
- ✅ Options.cs - Analysis options, units, convergence parameters, demand model settings
- ✅ TimeParameters.cs - Duration, timesteps, report timing, statistics
- ✅ Report.cs - Reporting flags and output configuration
- ❌ Energy.cs - Already refactored to network-wide only (no ScenarioId)
- ❌ Reaction.cs - Already refactored to network-wide only (no ScenarioId)
Design Discussion
Option 1: Options → Scenario FK (Current Pattern)
// Options model
public int? ScenarioId { get; set; } // Nullable (null = defaults)
public Scenario Scenario { get; set; }
// Usage pattern
var defaultOptions = options.FirstOrDefault(o => o.ScenarioId == null);
var scenarioOptions = options.FirstOrDefault(o => o.ScenarioId == scenarioId) ?? defaultOptions;
Pros:
- Matches current implementation pattern
- Simple 1:1 relationship
Cons:
- Inverted conceptual model ("Options belong to Scenario" vs "Scenario uses Options")
- Difficult to create reusable presets
- Complex queries to find effective options (scenario-specific or default)
- Cannot easily share configurations across scenarios
- UI would be more complex (create options per scenario vs select from presets)
Option 2: Scenario → Options FK (Recommended)
// Scenario model
public int? OptionsId { get; set; } // Nullable (null = use default)
public Options Options { get; set; }
// Options model
public string Name { get; set; } // "Default", "PDA Standard", etc.
public bool IsDefault { get; set; } // Mark the default set
public List<Scenario> Scenarios { get; set; } // Reverse navigation
Pros:
- ✅ Clearer conceptual model: "A scenario runs with a set of options"
- ✅ Enables reusable configuration presets (named option sets)
- ✅ Simpler queries:
scenario.Optionsinstead of complex lookups - ✅ Better UX: Dropdown to select existing preset or create new
- ✅ Flexibility to switch configurations without recreating scenarios
- ✅ Encourages best practices and standardization
Cons:
- Requires migration of existing data
- Breaking change to current pattern
Decision: Option 2 is strongly recommended.
Architectural Approach
Approach A: Individual FK References (Recommended)
// In Scenario.cs
public int? OptionsId { get; set; }
public Options Options { get; set; }
public int? TimeParametersId { get; set; }
public TimeParameters TimeParameters { get; set; }
public int? ReportId { get; set; }
public Report Report { get; set; }
Advantages:
- Maximum flexibility - mix and match different aspects
- Can share one configuration while customizing others
- Easier incremental migration (one model at a time)
- Simpler model structure
- Clear separation of concerns
Example Use Cases:
- Scenario A: "Standard Time" + "PDA Options" + "Full Report"
- Scenario B: "Extended Time" + "PDA Options" + "Summary Report"
- Reuses the same "PDA Options" across both
Approach B: Unified Configuration Profile (Future Enhancement)
public class SimulationProfile {
public int Id { get; set; }
public string Name { get; set; }
public bool IsDefault { get; set; }
public int OptionsId { get; set; }
public int TimeParametersId { get; set; }
public int ReportId { get; set; }
// Navigation properties...
}
// Scenario just references the profile
public int? SimulationProfileId { get; set; }
Advantages:
- Single dropdown UI: "Select simulation profile"
- Logical grouping of related configurations
- Easier to share complete analysis templates
Disadvantages:
- More complex structure
- Less granular flexibility
- Can be added later as a convenience layer
Decision: Start with Approach A, optionally add Approach B later as a convenience wrapper.
Implementation Plan
Phase 1: Options Model Refactoring
Goal: Remove duplicate fields from Scenario, establish Scenario→Options FK relationship.
Step 1.1: Update Options Model
- Add
Namefield (string, max 100 chars) - e.g., "Default", "PDA Standard" - Add
Descriptionfield (string, nullable) - longer description - Add
IsDefaultfield (bool) - mark the system default - Remove
ScenarioIdfield - Remove
Scenarionavigation property - Add reverse navigation:
List<Scenario> Scenarios - Update constructors to remove ScenarioId parameter
- Update
SetDefaults()method to set Name = "Default"
Step 1.2: Update Scenario Model
- Add
OptionsIdfield (int?, nullable) - Add
Optionsnavigation property - Remove duplicate fields:
DemandModel(HydraulicAnalysisType enum)PressureExponent(double)MinimumPressure(double?)RequiredPressure(double?)
- Remove
HydraulicAnalysisTypeenum (use Options.DemandModel string instead)
Step 1.3: Create Data Migration Strategy
- Create migration script to:
- Create default Options record with
IsDefault = true - For each existing Scenario with PDA settings, find or create matching Options
- Set
Scenario.OptionsIdto appropriate Options.Id - Verify all scenarios have options (either explicit or will use default)
- Create default Options record with
- Write rollback script (in case of issues)
Step 1.4: Update API Layer
- Update
ScenarioController.cs:- GET scenarios: Include Options in response
- POST scenarios: Accept OptionsId or create new Options
- PUT scenarios: Allow changing OptionsId
- Create
OptionsController.cs:- GET /api/options - List all option presets
- GET /api/options/{id} - Get specific option set
- POST /api/options - Create new option preset
- PUT /api/options/{id} - Update option preset
- DELETE /api/options/{id} - Delete if not in use
- GET /api/options/defaults - Get default options
- Update DTOs to reflect new structure
- Update any services that reference removed Scenario fields
Step 1.5: Update Frontend
IMPORTANT: Model structure has changed significantly. Forms need reworking.
Scenario Form Changes
- Remove direct PDA fields from scenario create/edit forms:
- Remove DemandModel dropdown/selector
- Remove PressureExponent input
- Remove MinimumPressure input
- Remove RequiredPressure input
- Add Options selection:
- Add Options preset dropdown (fetch from
/api/options) - Show selected preset name and description
- Add "Create New Options" button inline
- Display read-only summary of selected options (DemandModel, pressure values)
- Add Options preset dropdown (fetch from
- Update form validation to remove PDA field validations (now handled server-side)
- Update scenario creation payload to send
OptionsIdinstead of PDA fields - Update scenario editing to allow changing
OptionsId
New Options Management UI
- Create
/optionsroute for Options management page - Create
OptionsList.jsxcomponent:- Display all available option presets in table/card view
- Show Name, Description, DemandModel, IsDefault flag
- Actions: Edit, Delete (if not in use), Set as Default
- Create
OptionsForm.jsxcomponent for create/edit:- Name field (required, max 100 chars)
- Description field (optional, max 500 chars)
- IsDefault checkbox
- All EPANET options fields (Units, Pressure, Headloss, etc.)
- Demand Model section (DDA/PDA selector)
- PDA-specific fields (show/hide based on DemandModel)
- Convergence parameters
- Quality analysis options
- Pattern and multiplier settings
- Create
OptionsDetailModal.jsxfor viewing option details - Add navigation menu item for Options management
Scenario Detail View Changes
- Display Options preset name (with link to options details)
- Show embedded options data (DemandModel, pressure values) read-only
- Add "Change Options" action to switch to different preset
- Show warning if scenario uses deleted/missing options
API Integration Updates
Update scenario API calls to include
OptionsIdin payloadCreate new Options API service:
getOptions()- List all presetsgetOption(id)- Get specific presetgetDefaultOptions()- Get default presetcreateOption(data)- Create new presetupdateOption(id, data)- Update presetdeleteOption(id)- Delete preset (if not in use)
Update scenario response DTO handlers to access options data from new structure:
// OLD:
const demandModel = scenario.demandModel;
const pressureExponent = scenario.pressureExponent;
// NEW:
const demandModel = scenario.demandModel; // Still available in DTO as embedded data
const pressureExponent = scenario.pressureExponent; // Still available in DTO as embedded data
const optionsName = scenario.optionsName; // New field
TypeScript/PropTypes Updates
- Update Scenario type definitions:
- Remove:
demandModel: HydraulicAnalysisType - Remove:
pressureExponent: number - Remove:
minimumPressure?: number - Remove:
requiredPressure?: number - Add:
optionsId?: number - Add:
optionsName?: string - Keep embedded data:
demandModel?: string,pressureExponent?: number, etc. (for display)
- Remove:
- Create new Options type definition:
interface Options {
id: number;
name: string;
description?: string;
isDefault: boolean;
units?: string;
pressure?: string;
headloss?: string;
demandModel?: string;
minimumPressure?: number;
requiredPressure?: number;
pressureExponent?: number;
// ... all other options fields
createdAt: Date;
modifiedAt?: Date;
}
User Experience Considerations
- Provide helpful defaults when creating scenarios (auto-select default options)
- Show clear indicators when using custom vs default options
- Implement preset templates (e.g., "PDA Standard", "DDA High Accuracy")
- Add tooltips explaining what each options preset is for
- Implement search/filter for options presets list
- Add "Duplicate Options" feature to create variants of existing presets
Step 1.6: Testing
- Unit tests for new relationships
- Integration tests for migration
- API endpoint tests
- Frontend E2E tests
Phase 2: TimeParameters Model Refactoring
Goal: Establish Scenario→TimeParameters FK for reusable time configurations.
Step 2.1: Update TimeParameters Model
- Add
Namefield - e.g., "24 Hour Analysis", "Week Long Study" - Add
Descriptionfield - Add
IsDefaultfield - Remove
ScenarioIdfield - Remove
Scenarionavigation property - Add reverse navigation:
List<Scenario> Scenarios
Example Presets:
- "24 Hour Analysis" - Duration=24h, Hydraulic=1h, Quality=5min
- "Week Long Study" - Duration=168h, Hydraulic=6h, Quality=15min
- "High Resolution" - Duration=24h, Hydraulic=15min, Quality=1min
Step 2.2: Update Scenario Model
- Add
TimeParametersIdfield (int?, nullable) - Add
TimeParametersnavigation property
Step 2.3: Create Data Migration
- Create default TimeParameters
- Migrate existing ScenarioId relationships
- Update foreign keys
Step 2.4: Update API Layer
- Create
TimeParametersController.cs - Update
ScenarioController.csto include TimeParameters - Update DTOs
Step 2.5: Update Frontend
- Add TimeParameters dropdown to scenario forms
- Create TimeParameters preset management UI
Step 2.6: Testing
- Full test suite
Phase 3: Report Model Refactoring
Goal: Establish Scenario→Report FK for reusable report configurations.
Step 3.1: Update Report Model
- Add
Namefield - e.g., "Full Report", "Summary Only" - Add
Descriptionfield - Add
IsDefaultfield - Remove
ScenarioIdfield - Remove
Scenarionavigation property - Add reverse navigation:
List<Scenario> Scenarios
Example Presets:
- "Full Report" - All flags enabled
- "Summary Only" - Minimal output
- "Node Focus" - Detailed node data, minimal link data
- "Energy Analysis" - Energy reporting enabled
Step 3.2: Update Scenario Model
- Add
ReportIdfield (int?, nullable) - Add
Reportnavigation property
Step 3.3: Create Data Migration
- Create default Report
- Migrate existing relationships
- Update foreign keys
Step 3.4: Update API Layer
- Create
ReportController.cs - Update
ScenarioController.csto include Report - Update DTOs
Step 3.5: Update Frontend
- Add Report dropdown to scenario forms
- Create Report preset management UI
Step 3.6: Testing
- Full test suite
Phase 4: Database Context Updates
For each phase:
Step 4.1: Update DbContext
- Update
EN2PostgresContext(or relevant context) - Configure new relationships using Fluent API
- Set up cascading behavior (restrict delete if in use)
Step 4.2: Generate EF Core Migrations
- Run
dotnet ef migrations add RefactorOptions(Phase 1) - Run
dotnet ef migrations add RefactorTimeParameters(Phase 2) - Run
dotnet ef migrations add RefactorReport(Phase 3) - Review generated migration code
- Add custom migration logic for data preservation
Step 4.3: Update Seeds/Defaults
- Create seed data for default configurations
- Ensure IsDefault flags are set correctly
- Add common preset configurations
Phase 5: Documentation and Cleanup
Step 5.1: Update Documentation
- Update API documentation (Swagger/OpenAPI)
- Update database schema documentation
- Create user guide for configuration presets
- Update developer onboarding docs
Step 5.2: Code Cleanup
- Remove obsolete code
- Remove old enum types (e.g., HydraulicAnalysisType)
- Clean up unused using statements
- Update code comments
Step 5.3: Performance Review
- Profile query performance with new relationships
- Add indexes if needed
- Optimize eager loading patterns
Expected Benefits
For Users
- Reusable Presets: Create named configuration sets and reuse across scenarios
- Faster Setup: Select "PDA Standard" instead of configuring each field
- Consistency: Standardized configurations reduce errors
- Flexibility: Mix and match different configuration aspects
- Discovery: Browse available presets to learn best practices
For Developers
- Single Source of Truth: No more duplicate fields
- Cleaner Code: Remove redundant enum types
- Better Relationships: Clear FK references instead of nullable patterns
- Easier Queries: Direct navigation properties
- Extensibility: Easy to add new configuration types
For System
- Data Integrity: Proper FK constraints
- Reduced Duplication: Shared configurations stored once
- Easier Migrations: Clear ownership model
- Better Performance: Can optimize with proper indexes
Migration Strategy
Backward Compatibility Considerations
- API Versioning: Consider API version bump if breaking changes
- Deprecation Period: Mark old fields as obsolete before removal
- Dual Support: Temporarily support both old and new patterns during migration
- Client Updates: CRITICAL - Frontend MUST be updated simultaneously with backend
- Frontend forms expect different model structure
- Cannot deploy backend without frontend updates
- Coordinate deployment timing carefully
- Consider feature flag to roll out gradually
Frontend Migration Strategy
Breaking Change Alert: The frontend forms directly manipulate PDA fields that no longer exist on the Scenario model.
Option A: Simultaneous Deployment (Recommended for smaller teams)
- Complete all backend changes
- Complete all frontend changes
- Deploy backend and frontend together in single release
- Requires coordination window (low/no traffic period)
Option B: Graceful Migration (Recommended for larger teams/production)
- Phase 1: Backend adds new fields, keeps old fields temporarily
- Dual-write: Write to both Scenario fields AND Options
- API reads from Options, but Scenario fields still populated
- Frontend continues to work unchanged
- Phase 2: Update frontend to use new structure
- Deploy new frontend that uses OptionsId
- Old frontend still works (reading from scenario fields)
- Phase 3: Remove old scenario fields
- All clients updated to new frontend
- Remove duplicate fields from Scenario
- Remove dual-write logic
Frontend Deployment Checklist
- Verify all scenario forms updated
- Verify options management UI complete
- Test scenario creation with new structure
- Test scenario editing with new structure
- Test backward compatibility (if applicable)
- Update environment variables if needed
- Build production bundle
- Deploy to staging first
- QA testing on staging
- Deploy to production (coordinate with backend)
- Monitor for frontend errors
- Monitor API calls for 404s/validation errors
Data Preservation
- No Data Loss: Migration must preserve all existing scenario configurations
- Default Handling: Scenarios without explicit options must use defaults
- Validation: Post-migration validation to ensure all scenarios are functional
- Backup: Full database backup before migration
Rollback Plan
- Keep Old Columns: Initially add new columns without removing old ones
- Dual Write: Write to both old and new patterns during transition
- Validation Period: Run in dual-write mode for testing period
- Cutover: Once validated, switch to new pattern only
- Final Cleanup: Remove old columns only after successful production run
Risk Assessment
High Risk
Data Loss: Migration could lose scenario configurations
- Mitigation: Comprehensive testing, backup, rollback plan
API Breaking Changes: Clients may break
- Mitigation: API versioning, deprecation notices, documentation
Medium Risk
Performance Impact: New FK relationships could affect query performance
- Mitigation: Proper indexing, query optimization, load testing
Frontend Impact: UI changes required
- Mitigation: Coordinate releases, feature flags
Low Risk
- Learning Curve: Users need to understand preset concept
- Mitigation: Good UX, documentation, examples
Success Criteria
Phase 1 (Options)
- All duplicate fields removed from Scenario
- All scenarios have valid Options (explicit or default)
- API endpoints functional with new structure
- Frontend UI updated and tested
- No data loss verified
- Performance meets or exceeds current baseline
Phase 2 (TimeParameters)
- Scenario→TimeParameters FK established
- Reusable time presets available
- API and UI updated
Phase 3 (Report)
- Scenario→Report FK established
- Reusable report presets available
- API and UI updated
Overall
- All tests passing
- Documentation complete
- Production deployment successful
- User acceptance confirmed
Timeline Estimate
Phase 1 (Options): 2-3 weeks
- Model changes: 2-3 days
- Migration scripts: 2-3 days
- API updates: 3-4 days
- Frontend updates: 4-5 days
- Testing: 3-4 days
Phase 2 (TimeParameters): 1-2 weeks (faster due to pattern established)
- Model changes: 1-2 days
- Migration scripts: 1-2 days
- API updates: 2-3 days
- Frontend updates: 2-3 days
- Testing: 2-3 days
Phase 3 (Report): 1-2 weeks (similar to Phase 2)
- Model changes: 1-2 days
- Migration scripts: 1-2 days
- API updates: 2-3 days
- Frontend updates: 2-3 days
- Testing: 2-3 days
Phase 4 (DB Context): Integrated into each phase above
Phase 5 (Documentation): 1 week
- Documentation: 3-4 days
- Cleanup: 2-3 days
Total Estimated Time: 6-9 weeks (can be parallelized if multiple developers)
Related Files
Backend (C#)
efcorelibraries/HTModels/Models/EN2Models/Scenario.csefcorelibraries/HTModels/Models/EN2Models/Options.csefcorelibraries/HTModels/Models/EN2Models/TimeParameters.csefcorelibraries/HTModels/Models/EN2Models/Report.csefcorelibraries/EN2PostgresContext/(or relevant DbContext)dwd-api-aspnet/DwdApiAspNet/Controllers/ScenarioController.cs- New:
dwd-api-aspnet/DwdApiAspNet/Controllers/OptionsController.cs - New:
dwd-api-aspnet/DwdApiAspNet/Controllers/TimeParametersController.cs - New:
dwd-api-aspnet/DwdApiAspNet/Controllers/ReportController.cs
Frontend (React)
dwd-frontend-react/src/components/scenarios/- Scenario forms/components (NEEDS REWORK)dwd-frontend-react/src/components/scenarios/ScenarioForm.jsx- Remove PDA fields, add Options dropdowndwd-frontend-react/src/components/scenarios/ScenarioDetail.jsx- Display options info- New:
dwd-frontend-react/src/components/options/- Options management components - New:
dwd-frontend-react/src/components/options/OptionsList.jsx - New:
dwd-frontend-react/src/components/options/OptionsForm.jsx - New:
dwd-frontend-react/src/components/options/OptionsDetailModal.jsx - New:
dwd-frontend-react/src/services/optionsService.js- API calls for Options dwd-frontend-react/src/types/- TypeScript types/PropTypes (NEEDS UPDATE)dwd-frontend-react/src/routes/- Add Options route
Database
- Migration scripts
- Seed data scripts
Future Enhancements
Optional: SimulationProfile Wrapper (Approach B)
After completing Phases 1-3, could add a SimulationProfile model that bundles Options + TimeParameters + Report for complete scenario templates:
public class SimulationProfile {
public int Id { get; set; }
public string Name { get; set; } // "Standard 24hr PDA", "Extended Week DDA"
public string Description { get; set; }
public bool IsDefault { get; set; }
public int OptionsId { get; set; }
public Options Options { get; set; }
public int TimeParametersId { get; set; }
public TimeParameters TimeParameters { get; set; }
public int ReportId { get; set; }
public Report Report { get; set; }
}
This would provide both granular control (individual FKs) and convenience (bundled profiles).
Import/Export Configuration Presets
- Export preset configurations to JSON
- Import community-shared presets
- Version control for presets
Configuration Templates
- Industry-standard templates (AWWA guidelines, etc.)
- Organization-specific templates
- Template validation rules
Conclusion
This refactoring will significantly improve the maintainability and usability of the scenario configuration system by:
- Eliminating duplicate fields
- Establishing clear ownership relationships
- Enabling reusable configuration presets
- Providing a better user experience
The phased approach allows for incremental delivery and reduces risk. Starting with the Options model addresses the immediate duplication issue while establishing the pattern for subsequent phases.
Approvals
- Architecture Review
- Database Review
- API Team Review
- Frontend Team Review
- QA Team Review
- Product Owner Approval
Document History:
- 2025-10-15: Initial design discussion and implementation plan created