# MonthWise Forecast

A practical, transparent demand forecasting method built for real-world messy data.

MonthWise was born from frustration with traditional forecasting models (ETS, Prophet, ARIMA) that fail silently across hundreds of materials with different history lengths, sparse data, and seasonal shifts. Instead of fitting one model to an entire time series, MonthWise treats **each calendar month as its own independent forecasting problem**.

## Installation
```bash
pip install monthwise-forecast
```

Or install from source:
```bash
git clone https://github.com/your-repo/monthwise-forecast.git
cd monthwise-forecast
pip install .
```

## Quick Start
```python
from monthwise_forecast import MonthWiseForecaster
import pandas as pd

# Create monthly data
dates = pd.date_range("2023-01-01", periods=24, freq="MS")
values = [10, 12, 8, 15, 20, 18, 11, 14, 9, 16, 22, 19,
          12, 15, 10, 17, 24, 21, 13, 16, 11, 18, 26, 23]
series = pd.Series(values, index=dates)

# Forecast next 18 months
f = MonthWiseForecaster()
result = f.forecast(series)
print(result)

# Forecast 36 months in 6-month chunks
f = MonthWiseForecaster(forecast_horizon=36)
chunks = f.forecast_chunked(series, chunk_size=6)
print(chunks)
```

## How It Works

For each future month, MonthWise counts how many same-calendar-month data points exist in the history and applies the appropriate rule:

### Rule 1: One Historical Value

When only one same-month value exists, apply incremental growth:

- 1st future occurrence: value × (1 + growth_step)
- 2nd future occurrence: value × (1 + growth_step × 2)
- 3rd future occurrence: value × (1 + growth_step × 3)

Default growth_step is 0.10 (10%). This is the only configurable rule parameter.

**Example** (growth_step=0.10, March 2025 = 10):

| Future Month | Calculation | Forecast |
|---|---|---|
| March 2026 | 10 × 1.10 | 11 |
| March 2027 | 10 × 1.20 | 12 |
| March 2028 | 10 × 1.30 | 13 |

### Rule 2: Two Historical Values

Calculate the percentage change between the two values, cap at ±50%, and compound forward.

**Example** (April 2024 = 10, April 2025 = 12, pct_change = +20%):

| Future Month | Calculation | Forecast |
|---|---|---|
| April 2026 | 12 × 1.20 | 14 |
| April 2027 | 14.4 × 1.20 | 17 |

If the older value is 0, falls back to Rule 1 logic.

### Rule 3: Three or More Historical Values

Fit a simple linear regression (year vs value) and project the line forward.

- Capped at 1.5× the maximum historical value for that month
- Floored at 0

### Global Rules

- **Floor at 0**: No forecast is ever negative
- **Float math**: All calculations use floats; rounding to nearest integer happens only at the very end
- **Independent months**: Each calendar month determines its own rule based on its own data count
- **Minimum 12 months**: Will not forecast with less than 12 months of history

## API Reference

### MonthWiseForecaster(forecast_horizon=18, rule1_growth_step=0.10)

**Parameters:**
- forecast_horizon (int): Months to forecast. Default: 18
- rule1_growth_step (float): Growth increment per step for Rule 1. Default: 0.10

### .forecast(series) → pd.DataFrame

Forecast a single time series. Input: pd.Series with datetime index. Output: DataFrame with month_start and forecast columns.

### .forecast_chunked(series, chunk_size=6) → pd.DataFrame

Forecast and aggregate into chunks. Output: Single-row DataFrame with columns like 1-6, 7-12, etc.

### .forecast_batch(df, group_cols, date_col, value_col) → pd.DataFrame

Forecast multiple groups in a long-format DataFrame. Groups with < 12 months are skipped.

### .forecast_batch_chunked(df, group_cols, date_col, value_col, chunk_size=6) → pd.DataFrame

Forecast multiple groups and return chunked aggregates.

## Requirements

- Python >= 3.8
- numpy >= 1.20
- pandas >= 1.3
- scipy >= 1.7

## License

MIT
```