Skip to main content

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

  1. Field Duplication: Demand model and PDA-related fields are duplicated between two models:

    • Scenario.cs has: DemandModel, PressureExponent, MinimumPressure, RequiredPressure
    • Options.cs has: DemandModel, PressureExponent, MinimumPressure, RequiredPressure
  2. Unclear Relationship: The current Options model has a nullable ScenarioId FK pointing to Scenario, but this creates confusion about ownership and makes reusable presets difficult.

  3. 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)
// 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.Options instead 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

// 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 Name field (string, max 100 chars) - e.g., "Default", "PDA Standard"
  • Add Description field (string, nullable) - longer description
  • Add IsDefault field (bool) - mark the system default
  • Remove ScenarioId field
  • Remove Scenario navigation 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 OptionsId field (int?, nullable)
  • Add Options navigation property
  • Remove duplicate fields:
    • DemandModel (HydraulicAnalysisType enum)
    • PressureExponent (double)
    • MinimumPressure (double?)
    • RequiredPressure (double?)
  • Remove HydraulicAnalysisType enum (use Options.DemandModel string instead)

Step 1.3: Create Data Migration Strategy

  • Create migration script to:
    1. Create default Options record with IsDefault = true
    2. For each existing Scenario with PDA settings, find or create matching Options
    3. Set Scenario.OptionsId to appropriate Options.Id
    4. Verify all scenarios have options (either explicit or will use default)
  • 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)
  • Update form validation to remove PDA field validations (now handled server-side)
  • Update scenario creation payload to send OptionsId instead of PDA fields
  • Update scenario editing to allow changing OptionsId
New Options Management UI
  • Create /options route for Options management page
  • Create OptionsList.jsx component:
    • 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.jsx component 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.jsx for 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 OptionsId in payload

  • Create new Options API service:

    • getOptions() - List all presets
    • getOption(id) - Get specific preset
    • getDefaultOptions() - Get default preset
    • createOption(data) - Create new preset
    • updateOption(id, data) - Update preset
    • deleteOption(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)
  • 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 Name field - e.g., "24 Hour Analysis", "Week Long Study"
  • Add Description field
  • Add IsDefault field
  • Remove ScenarioId field
  • Remove Scenario navigation 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 TimeParametersId field (int?, nullable)
  • Add TimeParameters navigation 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.cs to 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 Name field - e.g., "Full Report", "Summary Only"
  • Add Description field
  • Add IsDefault field
  • Remove ScenarioId field
  • Remove Scenario navigation 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 ReportId field (int?, nullable)
  • Add Report navigation 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.cs to 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

  1. Reusable Presets: Create named configuration sets and reuse across scenarios
  2. Faster Setup: Select "PDA Standard" instead of configuring each field
  3. Consistency: Standardized configurations reduce errors
  4. Flexibility: Mix and match different configuration aspects
  5. Discovery: Browse available presets to learn best practices

For Developers

  1. Single Source of Truth: No more duplicate fields
  2. Cleaner Code: Remove redundant enum types
  3. Better Relationships: Clear FK references instead of nullable patterns
  4. Easier Queries: Direct navigation properties
  5. Extensibility: Easy to add new configuration types

For System

  1. Data Integrity: Proper FK constraints
  2. Reduced Duplication: Shared configurations stored once
  3. Easier Migrations: Clear ownership model
  4. Better Performance: Can optimize with proper indexes

Migration Strategy

Backward Compatibility Considerations

  1. API Versioning: Consider API version bump if breaking changes
  2. Deprecation Period: Mark old fields as obsolete before removal
  3. Dual Support: Temporarily support both old and new patterns during migration
  4. 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.

  1. Complete all backend changes
  2. Complete all frontend changes
  3. Deploy backend and frontend together in single release
  4. Requires coordination window (low/no traffic period)
  1. 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
  2. Phase 2: Update frontend to use new structure
    • Deploy new frontend that uses OptionsId
    • Old frontend still works (reading from scenario fields)
  3. 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

  1. No Data Loss: Migration must preserve all existing scenario configurations
  2. Default Handling: Scenarios without explicit options must use defaults
  3. Validation: Post-migration validation to ensure all scenarios are functional
  4. Backup: Full database backup before migration

Rollback Plan

  1. Keep Old Columns: Initially add new columns without removing old ones
  2. Dual Write: Write to both old and new patterns during transition
  3. Validation Period: Run in dual-write mode for testing period
  4. Cutover: Once validated, switch to new pattern only
  5. 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)

Backend (C#)

  • efcorelibraries/HTModels/Models/EN2Models/Scenario.cs
  • efcorelibraries/HTModels/Models/EN2Models/Options.cs
  • efcorelibraries/HTModels/Models/EN2Models/TimeParameters.cs
  • efcorelibraries/HTModels/Models/EN2Models/Report.cs
  • efcorelibraries/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 dropdown
  • dwd-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:

  1. Eliminating duplicate fields
  2. Establishing clear ownership relationships
  3. Enabling reusable configuration presets
  4. 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