Skip to main content

Streamlit App Architecture Analysis & Alternative Design Proposals

Table of Contents

  1. Current Streamlit App Design
  2. Architecture Analysis
  3. Alternative Design Proposals
  4. Comparison Matrix
  5. Recommendations

Current Streamlit App Design

Overview

The Streamlit application provides a web-based GUI for the WNTR (Water Network Tool for Resilience) package, enabling users to interact with EPANET water distribution network simulations through a browser interface.

Application Structure

streamlit_app/
├── main.py # Entry point, routing, sidebar navigation
├── components/
│ ├── home.py # Landing page with workflow guide
│ ├── network_builder.py # Network loading (examples/upload)
│ ├── simulation.py # Hydraulic simulation execution & visualization
│ └── pipe_crit.py # Pipe criticality analysis

Core Functionality

1. Network Builder (network_builder.py)

  • Purpose: Load and configure water network models
  • Features:
    • Load example networks from ../examples/networks/
    • Upload custom INP files
    • Display network topology visualization
    • Show network statistics (junctions, pipes, pumps, valves, reservoirs, tanks)
  • Data Flow:
    • File upload → Temporary file creation → WNTR model loading → Session state storage
    • Network object stored in st.session_state['network']

2. Simulation (simulation.py)

  • Purpose: Run hydraulic simulations and visualize results
  • Features:
    • Dual simulator support: EPANET (fast, PDD) and WNTR (slower, DD/PDD)
    • Configurable time parameters (duration, hydraulic step, pattern step, report step)
    • Multi-parameter visualization:
      • Pressure distribution (spatial + time series)
      • Flow rate distribution
      • Velocity distribution
      • Summary statistics
    • Data export (CSV downloads)
  • Data Flow:
    • User config → Simulation execution → Results storage → Visualization rendering
    • Results stored in st.session_state['results']

3. Pipe Criticality (pipe_crit.py)

  • Purpose: Analyze pipe failure impact on network resilience
  • Features:
    • Filter pipes by diameter (SI/Imperial units)
    • Set pressure thresholds
    • Simulate pipe closures
    • Impact zone mapping
    • Scenario save/load (JSON format)
    • Criticality ranking export
  • Data Flow:
    • Baseline simulation → Iterative pipe closure simulations → Impact calculation → Results visualization
    • Results stored in st.session_state['criticality_results']

4. Home (home.py)

  • Purpose: Landing page with workflow guidance and feature overview

Current Architecture Diagram

Data Flow: Simulation Execution

Data Flow: Criticality Analysis

Current Architecture Characteristics

Strengths

  • Rapid Development: Streamlit enables quick GUI creation with minimal code
  • Python Integration: Direct access to WNTR package without API layer
  • State Management: Session state provides simple persistence across interactions
  • Built-in Components: Pre-built widgets (file upload, sliders, plots) reduce development time
  • Single Language: Entire stack in Python simplifies maintenance

Limitations

  • Synchronous Execution: Long-running simulations block the UI thread
  • Limited Scalability: Single-threaded execution, no parallel processing
  • UI Constraints: Limited customization compared to modern web frameworks
  • State Management: Session state is server-side only, no client-side state
  • Real-time Updates: No WebSocket support for live progress updates
  • Concurrent Users: Each user session runs in the same process (potential resource contention)
  • Hosting Options: Limited to Streamlit Cloud or self-hosted servers

Alternative Design Proposals

Proposal 1: React Frontend + FastAPI Backend

Architecture Overview

Detailed Component Design

Frontend (React)

  • Technology Stack:

    • React 18+ with TypeScript
    • State Management: Redux Toolkit or Zustand
    • UI Framework: Material-UI, Ant Design, or Tailwind CSS
    • Charting: Chart.js, D3.js, or Plotly.js
    • Routing: React Router
    • HTTP Client: Axios or Fetch API
    • WebSocket Client: Socket.io-client or native WebSocket
  • Key Components:

    • NetworkUpload: File upload with drag-and-drop
    • NetworkViewer: Interactive network visualization (D3.js/vis.js)
    • SimulationConfig: Parameter configuration form
    • ResultsDashboard: Multi-tab results visualization
    • CriticalityAnalysis: Pipe selection and analysis interface
    • ProgressMonitor: Real-time progress updates via WebSocket

Backend (FastAPI)

  • Technology Stack:

    • FastAPI (async Python web framework)
    • Pydantic (data validation)
    • Celery (distributed task queue)
    • Redis (message broker + cache)
    • PostgreSQL (metadata storage)
    • SQLAlchemy (ORM)
    • WebSockets (real-time updates)
  • API Endpoints:

    POST   /api/networks/upload          # Upload INP file
    GET /api/networks/{id} # Get network metadata
    GET /api/networks/{id}/visualize # Get network graph data
    POST /api/simulations/run # Start simulation (async)
    GET /api/simulations/{id} # Get simulation status
    GET /api/simulations/{id}/results # Get simulation results
    POST /api/criticality/analyze # Start criticality analysis
    GET /api/criticality/{id}/status # Get analysis status
    GET /api/criticality/{id}/results # Get analysis results
    WS /ws/progress/{task_id} # WebSocket for progress
  • Task Queue Architecture:

    # Example Celery task
    @celery_app.task(bind=True)
    def run_simulation_task(self, network_id, config):
    # Update progress via WebSocket
    self.update_state(state='PROGRESS', meta={'progress': 0})

    # Load network
    wn = load_network(network_id)

    # Run simulation
    results = run_simulation(wn, config)

    # Store results
    store_results(network_id, results)

    return {'status': 'SUCCESS', 'results_id': results_id}

Data Flow: Async Simulation

Scalability Features

Horizontal Scaling:

  • Multiple FastAPI instances behind load balancer
  • Multiple Celery workers for parallel task execution
  • Redis cluster for distributed caching
  • PostgreSQL read replicas for query scaling

Parallel Processing:

# Parallel criticality analysis
from concurrent.futures import ProcessPoolExecutor

def run_parallel_criticality(wn, pipes, config):
with ProcessPoolExecutor(max_workers=4) as executor:
futures = {
executor.submit(analyze_pipe, wn, pipe, config): pipe
for pipe in pipes
}

results = {}
for future in as_completed(futures):
pipe = futures[future]
results[pipe] = future.result()

return results

Caching Strategy:

  • Network metadata cached in Redis (TTL: 1 hour)
  • Simulation results cached (TTL: 24 hours)
  • Network graph data cached (TTL: 1 hour)

Pros and Cons

Pros:

  • Modern UI/UX: Full control over user interface design
  • Async Processing: Non-blocking simulations with progress updates
  • Scalability: Horizontal scaling with load balancers and workers
  • Performance: Parallel processing for criticality analysis
  • Real-time Updates: WebSocket support for live progress
  • Separation of Concerns: Clear frontend/backend boundaries
  • API-First: Can support multiple clients (web, mobile, CLI)
  • Caching: Redis caching reduces redundant computations
  • Database: Persistent storage for networks and results
  • Production Ready: Industry-standard architecture

Cons:

  • Complexity: Significantly more complex than Streamlit
  • Development Time: Requires frontend and backend development
  • Deployment: More components to deploy and manage
  • Learning Curve: Team needs React/TypeScript and FastAPI knowledge
  • Infrastructure: Requires database, Redis, message queue
  • Cost: Higher hosting costs (multiple services)

Proposal 2: React Frontend + FastAPI Backend (Simplified)

A lighter-weight version of Proposal 1, suitable for smaller deployments.

Architecture Overview

Key Differences from Proposal 1

  • No Task Queue: Uses Python's ThreadPoolExecutor for background tasks
  • No Redis: Simple in-memory caching or SQLite for metadata
  • No WebSocket: Polling-based progress updates (simpler but less efficient)
  • Single Process: One FastAPI instance (can scale with multiple processes)

Pros and Cons

Pros:

  • Simpler: Fewer moving parts than full Proposal 1
  • Easier Deployment: Single application process
  • Lower Cost: No additional infrastructure (Redis, PostgreSQL)
  • Modern UI: Still benefits from React frontend
  • Async Support: Background tasks via ThreadPoolExecutor

Cons:

  • Limited Scalability: Single process limits concurrent users
  • No True Parallelism: GIL limits Python threading effectiveness
  • Polling Overhead: Less efficient than WebSocket for progress
  • State Management: In-memory state lost on restart

Proposal 3: Streamlit with Background Tasks (Enhanced Current)

Improve the current Streamlit app with async capabilities while keeping the Streamlit framework.

Architecture Overview

Enhancements

  1. Background Task Execution:

    import threading
    from queue import Queue

    task_queue = Queue()
    results_cache = {}

    def run_simulation_async(wn, config):
    task_id = str(uuid.uuid4())
    thread = threading.Thread(
    target=execute_simulation,
    args=(task_id, wn, config)
    )
    thread.start()
    return task_id
  2. Progress Polling:

    if 'task_id' in st.session_state:
    status = check_task_status(st.session_state['task_id'])
    if status['complete']:
    st.session_state['results'] = status['results']
    else:
    st.progress(status['progress'])
    time.sleep(1)
    st.rerun()
  3. Result Caching:

    • Cache simulation results to disk
    • Reuse results for same network/config combination

Pros and Cons

Pros:

  • Minimal Changes: Builds on existing codebase
  • Non-blocking: Simulations don't freeze UI
  • Familiar: Team already knows Streamlit
  • Quick Implementation: Faster than full rewrite

Cons:

  • Limited UI Control: Still constrained by Streamlit widgets
  • Scalability: Single process, limited concurrent users
  • No True Parallelism: Python GIL limits threading
  • Polling Overhead: Less efficient than WebSocket

Proposal 4: React Frontend + Azure Functions + Azure Storage (Serverless)

A cloud-native, serverless architecture using Azure services for maximum scalability and cost efficiency.

Architecture Overview

Detailed Component Design

Frontend (React)

  • Deployment: Azure Static Web Apps
  • Features: Same as Proposal 1/2, but optimized for Azure services
  • Real-time Updates: Azure SignalR Service for WebSocket connections

API Layer

  • Option A: Azure Functions HTTP Triggers (serverless)

  • Option B: Azure App Service with FastAPI (containerized)

  • API Endpoints:

    POST   /api/networks/upload          # Upload to Blob Storage
    GET /api/networks/{id} # Get from Table Storage
    POST /api/simulations/run # Enqueue to Queue Storage
    GET /api/simulations/{id} # Get status from Table Storage
    GET /api/simulations/{id}/results # Get results from Blob Storage
    POST /api/criticality/analyze # Enqueue criticality analysis

Azure Functions (Python)

  • Queue Trigger Function: Processes simulation tasks

    import azure.functions as func
    import wntr
    from azure.storage.blob import BlobServiceClient
    from azure.data.tables import TableServiceClient
    from azure.storage.queue import QueueServiceClient
    import json
    import pickle
    import tempfile
    import os

    def main(msg: func.QueueMessage) -> None:
    # Parse message
    task_data = json.loads(msg.get_body().decode('utf-8'))
    task_id = task_data['task_id']
    network_id = task_data['network_id']
    config = task_data['config']

    # Initialize Azure clients
    blob_client = BlobServiceClient.from_connection_string(...)
    table_client = TableServiceClient.from_connection_string(...)

    # Update status: Processing
    update_status(table_client, task_id, 'PROCESSING', progress=0)

    try:
    # Download INP file from Blob Storage
    inp_blob = blob_client.get_blob_client(
    container="networks",
    blob=f"{network_id}.inp"
    )
    with tempfile.NamedTemporaryFile(delete=False, suffix='.inp') as tmp:
    inp_blob.download_blob().readinto(tmp)
    tmp_path = tmp.name

    # Load network
    wn = wntr.network.WaterNetworkModel(tmp_path)

    # Configure simulation
    wn.options.time.duration = config['duration'] * 3600
    wn.options.time.hydraulic_timestep = config['hydraulic_step']
    # ... other config

    # Run simulation
    if config['sim_type'] == 'EPANET':
    sim = wntr.sim.EpanetSimulator(wn)
    else:
    sim = wntr.sim.WNTRSimulator(wn)

    results = sim.run_sim()

    # Serialize and upload results to Blob Storage
    results_blob = blob_client.get_blob_client(
    container="results",
    blob=f"{task_id}.pkl"
    )
    with tempfile.NamedTemporaryFile(delete=False) as tmp:
    pickle.dump(results, tmp)
    results_blob.upload_blob(open(tmp.name, 'rb'), overwrite=True)

    # Update status: Complete
    update_status(table_client, task_id, 'COMPLETE', progress=100,
    results_blob=f"{task_id}.pkl")

    # Send SignalR notification
    send_signalr_notification(task_id, 'COMPLETE')

    except Exception as e:
    update_status(table_client, task_id, 'FAILED', error=str(e))
    send_signalr_notification(task_id, 'FAILED', error=str(e))
    finally:
    # Cleanup
    if os.path.exists(tmp_path):
    os.remove(tmp_path)
  • HTTP Trigger Function: Handles API requests

    import azure.functions as func
    from azure.storage.queue import QueueServiceClient
    import json
    import uuid

    def main(req: func.HttpRequest) -> func.HttpResponse:
    # Parse request
    network_id = req.route_params.get('network_id')
    config = req.get_json()

    # Create task
    task_id = str(uuid.uuid4())
    task_data = {
    'task_id': task_id,
    'network_id': network_id,
    'config': config,
    'created_at': datetime.utcnow().isoformat()
    }

    # Enqueue to Azure Queue Storage
    queue_client = QueueServiceClient.from_connection_string(...)
    queue = queue_client.get_queue_client('simulation-tasks')
    queue.send_message(json.dumps(task_data))

    # Store initial status in Table Storage
    table_client = TableServiceClient.from_connection_string(...)
    table = table_client.get_table_client('simulations')
    entity = {
    'PartitionKey': network_id,
    'RowKey': task_id,
    'Status': 'QUEUED',
    'Progress': 0,
    'Config': json.dumps(config)
    }
    table.upsert_entity(entity)

    return func.HttpResponse(
    json.dumps({'task_id': task_id, 'status': 'QUEUED'}),
    status_code=202,
    mimetype='application/json'
    )

Azure Storage Services

  1. Azure Queue Storage

    • Purpose: Task queue for simulations
    • Message Format: JSON with task_id, network_id, config
    • Visibility Timeout: 5 minutes (adjust based on simulation duration)
    • Dead Letter Queue: Failed tasks after max retries
  2. Azure Blob Storage

    • Container: networks: INP files (one per network)
    • Container: results: Serialized simulation results (pickle/parquet)
    • Container: exports: CSV/JSON exports for download
    • Lifecycle Policy: Archive old results after 90 days
  3. Azure Table Storage

    • Table: networks: Network metadata

      • PartitionKey: Network ID
      • RowKey: Timestamp
      • Columns: filename, uploaded_at, num_nodes, num_links, etc.
    • Table: simulations: Simulation status and metadata

      • PartitionKey: Network ID
      • RowKey: Task ID
      • Columns: status, progress, config, results_blob, created_at, completed_at
    • Table: criticality_analyses: Criticality analysis status

      • PartitionKey: Network ID
      • RowKey: Analysis ID
      • Columns: status, config, results_blob, impacted_pipes

Azure SignalR Service

  • Purpose: Real-time progress updates

  • Implementation:

    from azure.functions import SignalRMessage

    def send_progress(task_id: str, progress: int):
    signalr_client.send_to_all({
    'type': 'progress',
    'task_id': task_id,
    'progress': progress
    })

Data Flow: Azure Serverless Simulation

Parallel Processing: Criticality Analysis

For criticality analysis, multiple queue messages can be enqueued for parallel processing:

# Enqueue multiple tasks for parallel processing
def enqueue_criticality_analysis(network_id, pipes, config):
queue_client = QueueServiceClient.from_connection_string(...)
queue = queue_client.get_queue_client('criticality-tasks')

tasks = []
for pipe in pipes:
task_id = str(uuid.uuid4())
task_data = {
'task_id': task_id,
'network_id': network_id,
'pipe_name': pipe,
'config': config,
'analysis_id': analysis_id # Group related tasks
}
queue.send_message(json.dumps(task_data))
tasks.append(task_id)

return tasks

# Function app processes each pipe independently
# Results are aggregated by analysis_id

Scalability Features

Auto-scaling:

  • Azure Functions automatically scale based on queue depth
  • Each function instance processes one message at a time
  • Can handle hundreds of concurrent simulations

Cost Optimization:

  • Consumption Plan: Pay per execution (good for variable load)
  • Premium Plan: Pre-warmed instances (good for consistent load)
  • Dedicated Plan: Always-on instances (good for high throughput)

Storage Optimization:

  • Blob Tiering: Hot → Cool → Archive based on access patterns
  • Table Storage: Partition by network_id for efficient queries
  • Lifecycle Management: Auto-archive old results

Pros and Cons

Pros:

  • Serverless: No infrastructure management
  • Auto-scaling: Handles traffic spikes automatically
  • Cost-Effective: Pay only for compute time used
  • High Availability: Azure SLA guarantees
  • Parallel Processing: Multiple function instances process tasks concurrently
  • Managed Services: Azure handles queue, storage, and messaging
  • Global Scale: Can deploy to multiple Azure regions
  • No Cold Starts (Premium): Pre-warmed instances available
  • Integrated Monitoring: Azure Application Insights built-in
  • Disaster Recovery: Built-in backup and replication

Cons:

  • Vendor Lock-in: Tied to Azure ecosystem
  • Cold Starts: Consumption plan has function cold start latency
  • Timeout Limits: Functions have execution time limits (10 min default, 30 min max)
  • Package Size: Large dependencies (WNTR, EPANET) increase deployment package size
  • Debugging: More complex debugging than local development
  • Cost at Scale: Can be expensive with high throughput
  • Learning Curve: Team needs Azure knowledge
  • Network Latency: Blob storage I/O adds latency

Azure-Specific Considerations

Function App Configuration:

# requirements.txt for Function App
azure-functions
azure-storage-blob
azure-storage-queue
azure-data-tables
azure-signalr
wntr
numpy
pandas
matplotlib

host.json Configuration:

{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[3.*, 4.0.0)"
},
"functionTimeout": "00:30:00",
"queues": {
"maxPollingInterval": "00:00:02",
"batchSize": 1,
"maxDequeueCount": 3
}
}

Storage Account Configuration:

  • Performance: Standard (HDD) or Premium (SSD) based on I/O needs
  • Replication: LRS (local) for dev, GRS (geo-redundant) for production
  • Access Tier: Hot for active data, Cool for archival

Cost Estimation Example:

  • Functions: ~$0.000016 per GB-second (Consumption plan)
  • Queue Storage: ~$0.0004 per 10,000 operations
  • Blob Storage: ~$0.0184 per GB/month (Hot tier)
  • Table Storage: ~$0.054 per GB/month
  • SignalR: ~$0.10 per million messages

For 1000 simulations/month (avg 2 min each):

  • Functions: ~$0.50
  • Storage: ~$5-10 (depending on result sizes)
  • Total: ~$10-15/month (very cost-effective)

Storage Strategy: Table Storage vs Blob Storage

When to Use Table Storage:

  • Metadata: Network info, simulation status, task tracking
  • Small Data: < 1 MB per entity
  • Query Patterns: Filter by network_id, status, date ranges
  • Fast Lookups: O(1) access by PartitionKey/RowKey
  • Structured Data: JSON-serializable metadata

Example Table Storage Schema:

# simulations table entity
{
'PartitionKey': 'network_123', # Network ID
'RowKey': 'task_456', # Task ID
'Status': 'COMPLETE', # QUEUED, PROCESSING, COMPLETE, FAILED
'Progress': 100,
'Config': '{"duration": 24, "sim_type": "EPANET"}',
'ResultsBlob': 'results/task_456.pkl',
'CreatedAt': '2024-01-15T10:00:00Z',
'CompletedAt': '2024-01-15T10:05:00Z',
'Error': None
}

When to Use Blob Storage:

  • Large Files: INP files, serialized results (pickle/parquet)
  • Binary Data: Simulation results, network files
  • Infrequent Access: Archive old results
  • Streaming: Download results without loading into memory
  • Lifecycle Management: Auto-tier to Cool/Archive

Example Blob Storage Structure:

networks/
├── network_123.inp
├── network_456.inp

results/
├── task_789.pkl # Full simulation results
├── task_789_pressure.csv # Exported CSV
├── task_789_flow.csv

criticality/
├── analysis_abc_results.pkl
├── analysis_abc_rankings.csv

Hybrid Approach (Recommended):

  • Table Storage: Store metadata, status, and references to blob paths
  • Blob Storage: Store actual data files (INP, results, exports)
  • Query Pattern: Query Table Storage for status → Retrieve from Blob Storage when needed

Handling Long-Running Simulations

Azure Functions have a maximum timeout of 30 minutes (10 minutes default). For longer simulations:

Option 1: Split into Chunks

# For simulations > 30 minutes, split into time segments
def run_long_simulation(network_id, total_duration):
chunk_duration = 20 # minutes (safe margin)
chunks = total_duration // chunk_duration

for i in range(chunks):
chunk_start = i * chunk_duration * 3600
chunk_end = min((i + 1) * chunk_duration * 3600, total_duration * 3600)

# Enqueue chunk task
enqueue_chunk_task(network_id, chunk_start, chunk_end, chunk_id=i)

# Final aggregation function combines chunks

Option 2: Use Durable Functions

import azure.functions as func
import azure.durable_functions as df

def orchestrator_function(context: df.DurableOrchestrationContext):
# Long-running simulation can be broken into steps
# Each step can checkpoint progress
result1 = yield context.call_activity('run_simulation_chunk', chunk1)
result2 = yield context.call_activity('run_simulation_chunk', chunk2)
final_result = yield context.call_activity('aggregate_results', [result1, result2])
return final_result

Option 3: Use Azure Container Instances

For very long simulations, use Azure Container Instances instead of Functions:

  • No timeout limits
  • Can run for hours/days
  • Triggered by Queue Storage messages
  • More expensive but necessary for long tasks

Comparison Matrix

FeatureCurrent StreamlitProposal 1 (Full)Proposal 2 (Simplified)Proposal 3 (Enhanced)Proposal 4 (Azure)
UI CustomizationLimitedFull ControlFull ControlLimitedFull Control
Development TimeFastSlowMediumMediumMedium-Slow
ScalabilityLowHighMediumLowVery High (Auto-scale)
Async ProcessingNoYes (Celery)Yes (ThreadPool)Yes (Threading)Yes (Queue Storage)
Real-time UpdatesNoYes (WebSocket)PollingPollingYes (SignalR)
Parallel ProcessingNoYes (Multi-worker)LimitedLimitedYes (Multi-instance)
Deployment ComplexityLowHighMediumLowMedium (Azure-specific)
Infrastructure CostLowHighLowLowLow (Pay-per-use)
Infrastructure ManagementLowHighMediumLowNone (Serverless)
Learning CurveLowHighMediumLowMedium-High (Azure)
API-FirstNoYesYesNoYes
State ManagementSession StateDatabase + CacheSQLite + MemorySession StateTable + Blob Storage
Production ReadyLimitedYesMediumLimitedYes (Enterprise-grade)
MaintenanceEasyComplexMediumEasyLow (Managed services)
Vendor Lock-inNoneNoneNoneNoneAzure
Cold Start LatencyN/AN/AN/AN/AYes (Consumption plan)
Timeout LimitsNoneNoneNoneNone30 min max

Recommendations

For Small Teams / Prototypes

Recommendation: Proposal 3 (Enhanced Streamlit)

  • Quick wins with minimal infrastructure changes
  • Maintains familiarity with existing codebase
  • Good for internal tools or demos

For Production Applications (On-Premise / Self-Hosted)

Recommendation: Proposal 2 (Simplified React + FastAPI)

  • Best balance of modern UI and manageable complexity
  • Can scale to multiple processes if needed
  • No additional infrastructure beyond the application server
  • Suitable for 10-50 concurrent users
  • No vendor lock-in

For Enterprise / High-Scale Applications (Self-Hosted)

Recommendation: Proposal 1 (Full React + FastAPI)

  • Maximum scalability and performance
  • Industry-standard architecture
  • Supports multiple client types (web, mobile, API consumers)
  • Requires dedicated DevOps resources
  • Full control over infrastructure

For Cloud-Native / Serverless Applications

Recommendation: Proposal 4 (Azure Functions + Storage)

  • Best for: Organizations already using Azure or wanting serverless architecture
  • Benefits:
    - Zero infrastructure management
    - Auto-scaling handles traffic spikes
    - Pay-per-use cost model (very cost-effective for variable load)
    - Enterprise-grade reliability and SLA
    - Built-in monitoring and logging
  • Considerations:
    - Requires Azure expertise
    - Vendor lock-in to Azure
    - Function timeout limits (30 min max) may require splitting long simulations
    - Cold start latency on Consumption plan (use Premium for consistent performance)
  • Ideal Use Cases:
    - Variable or unpredictable load
    - Need for high availability without DevOps overhead
    - Cost optimization for sporadic usage
    - Multi-region deployment requirements

Migration Path

If starting with Proposal 2 or 3, you can migrate to Proposal 1 incrementally:

  1. Phase 1: Implement FastAPI backend with basic endpoints
  2. Phase 2: Add React frontend for one feature (e.g., Simulation)
  3. Phase 3: Migrate remaining features incrementally
  4. Phase 4: Add task queue and WebSocket support
  5. Phase 5: Add database and caching layer

Technical Considerations

Threading vs Multiprocessing

Threading (Proposal 2, 3):

  • Limited by Python GIL for CPU-bound tasks
  • Good for I/O-bound operations
  • Shared memory (easier state sharing)
  • Lower overhead

Multiprocessing (Proposal 1):

  • True parallelism for CPU-bound tasks
  • Separate memory spaces (requires serialization)
  • Higher overhead
  • Better for heavy computations (simulations)

Recommendation: Use multiprocessing for WNTR simulations (CPU-intensive), threading for I/O operations.

Caching Strategy

Network Metadata:

  • Cache parsed network structure (nodes, links, attributes)
  • TTL: 1 hour (networks rarely change)

Simulation Results:

  • Cache by (network_id, config_hash)
  • TTL: 24 hours
  • Store large results on disk, metadata in cache

Criticality Analysis:

  • Cache individual pipe analysis results
  • Can recompute in parallel if cache miss

Database Schema (Proposal 1/2)

-- Networks table
CREATE TABLE networks (
id UUID PRIMARY KEY,
filename VARCHAR(255),
uploaded_at TIMESTAMP,
metadata JSONB,
file_path VARCHAR(500)
);

-- Simulations table
CREATE TABLE simulations (
id UUID PRIMARY KEY,
network_id UUID REFERENCES networks(id),
config JSONB,
status VARCHAR(50),
created_at TIMESTAMP,
completed_at TIMESTAMP,
results_path VARCHAR(500)
);

-- Criticality analyses table
CREATE TABLE criticality_analyses (
id UUID PRIMARY KEY,
network_id UUID REFERENCES networks(id),
config JSONB,
status VARCHAR(50),
results JSONB
);

Conclusion

The current Streamlit app provides a functional GUI for WNTR, but has limitations in scalability, UI customization, and async processing. The proposed alternatives offer varying levels of improvement:

  • Proposal 1: Maximum capability, suitable for enterprise deployment
  • Proposal 2: Balanced approach, good for production with moderate scale
  • Proposal 3: Minimal changes, good for quick improvements

Choose based on your team size, deployment requirements, and expected user load.