# Parallel Campaigns Quota Allocation Simulator

A Streamlit-based simulation tool for evaluating different quota allocation strategies for parallel email campaigns sharing a common sending quota.

## Problem Statement

Email marketing platforms often need to send multiple campaigns simultaneously while respecting IP warmup rules and rate limits. When multiple campaigns compete for a fixed hourly email quota:

- **Current approach**: Split quota equally among all active campaigns
- **Issue**: New campaigns slow down existing ones significantly
- **Goal**: Find fairer allocation strategies that minimize disruption to existing campaigns

## Solution

This simulator implements and compares **6 different quota allocation strategies**:

1. **Equal Split** (baseline) - Divide quota equally among all active campaigns
2. **Proportional to Total Size** - Allocate based on each campaign's total size (fair, predictable)
3. **Proportional to Remaining** - Allocate based on remaining volume (prioritizes campaigns with larger backlogs)
4. **First-Come-First-Served (FCFS)** - Prioritize campaigns that started earlier
5. **Weighted Fair Queuing** - Balance fairness using age-based weights
6. **Priority+Spillover** - Configurable split (default 90/10) between FCFS priority and equal distribution

## Features

### Interactive Streamlit UI
- **Scenario Management**: Load presets, add campaigns manually, or generate random scenarios
- **Strategy Comparison**: Run multiple strategies side-by-side
- **Rich Visualizations**: Gantt charts, timeline views, quota split analysis
- **Fairness Metrics**: Slowdown factors, completion times, quota utilization

### Preset Scenarios
- Simultaneous Start
- Staggered Start
- Rush Hour (many small + few large campaigns)
- Early vs Late
- Varied Sizes (exponential growth)

### Customizable Parameters
- Quota per hour
- Priority/spillover percentage
- Campaign volumes and start times
- Random generation settings

## Installation

### Prerequisites
- Python 3.11+
- [uv](https://github.com/astral-sh/uv) package manager

### Setup

```bash
# Install dependencies
make install

# Or manually with uv
uv sync
```

## Usage

### Run the Streamlit App

```bash
# Using make
make run

# Or directly with uv
uv run streamlit run src/streamlit_app.py
```

### Run Tests

```bash
# Run the test implementation script
uv run python test_implementation.py
```

### Code Quality

```bash
# Format code
make fmt

# Check code quality
make check
```

## Project Structure

```
parallel-campaigns-simulation/
   src/
      __init__.py
      models.py              # Data models (Campaign, SimulationResult)
      strategies.py          # 5 quota allocation strategies
      simulator.py           # Simulation engine
      scenarios.py           # Scenario generation (presets + random)
      visualizations.py      # Plotly charts and graphs
      streamlit_app.py       # Main Streamlit UI
   test_implementation.py     # Test script
   Makefile                   # Development commands
   pyproject.toml            # Project configuration
   README.md                 # This file
```

## Key Concepts

### Quota Allocation
The system simulates minute-by-minute email sending with a fixed quota per minute (derived from hourly quota � 60).

### Fairness Metrics
- **Completion Time**: Minutes to finish sending all campaign emails
- **Slowdown Factor**: Completion time compared to running alone (lower is better)
- **Quota Utilization**: Percentage of available quota actually used

### Strategies Explained

#### 1. Equal Split
Divides available quota equally among all active campaigns.
- **Pros**: Simple, predictable, fair
- **Cons**: New campaigns slow down existing ones proportionally

#### 2. Proportional to Total Size
Allocates quota based on each campaign's **total volume** (original size).
- **Pros**: Fair and consistent; larger campaigns always get proportionally more quota
- **Cons**: Doesn't adapt to progress; small campaigns may wait longer
- **Use case**: When campaign size should determine priority throughout execution

#### 3. Proportional to Remaining
Allocates quota based on each campaign's **remaining volume**.
- **Pros**: Prioritizes campaigns with larger backlogs
- **Cons**: Penalizes campaigns that started earlier; can be counterintuitive
- **Use case**: When you want to "catch up" campaigns that are behind
- **⚠️ Warning**: Early campaigns get less quota after sending emails!

#### 4. First-Come-First-Served (FCFS)
Gives priority to campaigns that started earlier.
- **Pros**: No slowdown for first campaign; rewards being first
- **Cons**: Later campaigns must wait; can be unfair to late arrivals

#### 5. Weighted Fair Queuing
Uses age-based weights (square root of campaign age) to balance fairness.
- **Pros**: Gradual priority increase; more balanced than pure FCFS
- **Cons**: Still favors older campaigns, just less aggressively

#### 6. Priority+Spillover
Allocates configurable percentage (default 90%) to FCFS priority, remainder split equally.
- **Pros**: Customizable balance between efficiency and fairness; nearly optimal for first campaign
- **Cons**: Requires tuning the priority percentage
- **Best for**: Minimizing disruption to existing campaigns (5-10% slowdown with 90/10 split)

## Example Usage in Streamlit App

1. **Configure Quota**: Set emails per hour (e.g., 6000)
2. **Adjust Priority Split**: Set priority percentage for Strategy 5 (e.g., 90%)
3. **Select Strategies**: Choose which strategies to compare
4. **Load Scenario**:
   - Choose preset scenario, OR
   - Add campaigns manually, OR
   - Generate random campaigns
5. **Run Simulation**: Click "Run Simulation"
6. **Analyze Results**:
   - Compare completion times across strategies
   - View slowdown factors
   - Examine Gantt charts and quota splits
   - Drill into campaign-level details

## Test Results

The test script demonstrates key behaviors:

```
Strategy: First-Come-First-Served
  Early Campaign: 59 min (1.00x slowdown)
  Late Campaign: 104 min (1.76x slowdown)

Strategy: Priority+Spillover (90/10)
  Early Campaign: 62 min (1.05x slowdown)
  Late Campaign: 105 min (1.78x slowdown)
```

FCFS gives first campaign no slowdown but delays second significantly. Priority+Spillover (90/10) provides near-optimal performance for the first campaign with only 5% slowdown.

## Development

### Code Style
- Strictly typed Python (all type hints required)
- Passes `ruff` linting and formatting
- Docstrings for all public functions/classes

### Adding New Strategies

1. Create strategy class in `src/strategies.py`:

```python
class MyNewStrategy(QuotaStrategy):
    def get_name(self) -> str:
        return "My Strategy"

    def allocate_quota(
        self, campaigns: List[Campaign], minute: int, available_quota: int
    ) -> Dict[str, int]:
        # Your allocation logic here
        pass
```

2. Add to `get_all_strategies()` function
3. Strategy will automatically appear in UI

### Adding New Preset Scenarios

1. Add static method to `ScenarioGenerator` in `src/scenarios.py`
2. Add to `get_preset_scenarios()` dictionary
3. Scenario will automatically appear in UI dropdown

## Dependencies

Core dependencies (see `pyproject.toml` for full list):
- **streamlit** - Web UI framework
- **pandas** - Data manipulation
- **plotly** - Interactive visualizations
- **numpy** - Numerical computing
- **ruff** - Linting and formatting

## License

This project is for internal use in evaluating email campaign quota allocation strategies.

## Contributing

1. Ensure all changes pass `make check`
2. Add tests for new functionality
3. Update documentation as needed
