# Revenue Forecasting

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 116  
**Tier:** Business+  
**Depends on:** `invoices-core`, `recurring-invoices`, `marketing-leads-pipeline`, `projects-module`, `foundation-auth-rbac`  
**Referenced by:** `admin-reports-analytics`, `profitability-reports`

---

## Overview

Revenue forecasting aggregates committed and projected revenue across three sources: sent/issued invoices (committed), scheduled recurring invoices (scheduled), and active leads with estimated value (projected). Visualizes monthly revenue outlook for the next 12 months.

---

## Route

`/reports/revenue-forecast` (Business+)

---

## Summary Bar

```
┌──────────────────────────────────────────────────────────────┐
│  Revenue Forecast                     Period: [12 months ▾]  │
│                                                              │
│  Committed    Scheduled    Projected    Total                 │
│  ₪41,200      ₪18,000      ₪24,500      ₪83,700              │
│  (sent/issued) (recurring)  (leads)                          │
└──────────────────────────────────────────────────────────────┘
```

**Committed**: invoices with status `SENT | TAX_ISSUED | PARTIALLY_PAID` — overdue risk applied (past-due invoices discounted by configurable recovery rate, default 80%).  
**Scheduled**: recurring invoices where next generation date falls within forecast window (spec 53).  
**Projected**: leads with `stage NOT IN ('LOST', 'WON')` and `estimated_value IS NOT NULL` — probability-weighted by stage.

---

## Monthly Forecast Chart

Stacked bar chart (Recharts) showing monthly breakdown:

```
┌──────────────────────────────────────────────────────────────┐
│  Monthly Revenue Forecast                                    │
│                                                              │
│  ₪20k ┤                                                      │
│  ₪15k ┤     ██                                               │
│  ₪10k ┤  ████ ██ ██ ██ ██                                    │
│   ₪5k ┤████████████████████████████████████                  │
│    ₪0 └──────────────────────────────────────────────────→   │
│       Jun  Jul  Aug  Sep  Oct  Nov  Dec  Jan  Feb  Mar  Apr  │
│                                                              │
│  ■ Committed   ■ Scheduled   ■ Projected                     │
└──────────────────────────────────────────────────────────────┘
```

Hover tooltip shows breakdown per category for each month.

### Chart Accessibility (adopt spec 24 pattern)

`reports-analytics` (spec 24) defines the accessible chart pattern. Apply identically here:

1. Wrapper: `role="figure"` `aria-labelledby="{chart-id}-title"`
2. `<figcaption id="{chart-id}-title">` text matches the visual chart heading
3. Visually-hidden `<table>` sibling containing the same data in tabular form; "Show data table" toggle button adjacent to chart
4. SVG root: `<title>` (e.g., `"Forecast vs actuals: Jan–Dec 2025"`) + `<desc>` (trend: e.g., `"Forecast exceeded actuals in 8 of 12 months"`)
5. Tooltip: `aria-live="polite"` region echoing tooltip content on hover/focus
6. Interactive segments (clickable): focusable via Tab, activated via Enter/Space

### RTL Chart Configuration

```tsx
function ForecastChart({ locale }: { locale: string }) {
  const isRtl = locale === 'he-IL'
  return (
    <ResponsiveContainer>
      <ComposedChart>
        <YAxis orientation={isRtl ? 'right' : 'left'} />
        <XAxis orientation="bottom" />
        <Tooltip position={{ x: isRtl ? 'left' : 'right' }} />
      </ComposedChart>
    </ResponsiveContainer>
  )
}
// Line chart dots: coordinate-based — no RTL adjustment needed
```

---

## Lead Probability Weights

Default stage-to-probability mapping (configurable per tenant in `/settings/crm`):

| Stage | Default probability |
|-------|---------------------|
| NEW | 5% |
| CONTACTED | 15% |
| QUALIFIED | 30% |
| PROPOSAL | 60% |

Weighted projected value: `SUM(estimated_value * stage_probability)` per month (using `leads.created_at` as proxy for expected close month unless `reengagement_at` is set).

Stored in `tenant_settings.lead_stage_probabilities JSONB` (new column).

---

## Forecast Period

Selector: **3 months**, **6 months**, **12 months** (default), **24 months**.

For recurring invoices: expand RRULE for the full forecast window using `next_generation_date` from `recurring_invoice_templates` (same `rrule.js` as calendar/recurring tasks — spec 53).

For leads: bucket by expected close month. Default bucketing: leads with no explicit close date spread evenly over next 3 months for their stage.

---

## Data Table

Below chart: tabular breakdown per month, exportable:

```
Month       Committed    Scheduled    Projected    Total
Jun 2026    ₪8,200       ₪3,000       ₪4,100       ₪15,300
Jul 2026    ₪6,500       ₪3,000       ₪5,200       ₪14,700
Aug 2026    ₪4,200       ₪3,000       ₪6,800       ₪14,000
...
```

**[Export CSV]** — downloads monthly forecast table.

---

## Schema Delta

```sql
ALTER TABLE tenant_settings ADD COLUMN lead_stage_probabilities JSONB DEFAULT
  '{"NEW": 5, "CONTACTED": 15, "QUALIFIED": 30, "PROPOSAL": 60}';
-- Stage probability % used for lead weighted revenue forecast.
```

---

## API

```
GET /api/reports/revenue-forecast
    → forecast data for chart + table
      query: { months: number (3|6|12|24, default 12) }
      Returns: {
        summary: { committed, scheduled, projected, total },
        monthly: [{
          month: string,       // YYYY-MM
          committed: number,
          scheduled: number,
          projected: number,
          total: number
        }]
      }
      Requires: reports:read (Business+)
```

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Three-source forecast | Not invoice-only | Invoice-only misses future pipeline value; three categories give full picture at different confidence levels |
| Stage probability weights | Not binary won/lost | Probability weights enable weighted pipeline value — standard sales forecasting methodology; configurable so tenants tune to their close rates |
| RRULE expansion server-side | Not client-side | Forecast API returns pre-computed data; client shouldn't expand RRULEs; server has full rrule.js access |
| Business+ tier | Not all tiers | Forecasting requires recurring invoice data and lead pipeline value — these are Business+ features; bundling forecasting at same tier is consistent |
