# MGID Budget Reallocator

Identifies donor/recipient pairs for daily limit shifts. Rules and formulas live
**here** — simulator only merges results.

Tools: [../_shared/ads-mcp-tools.md](../_shared/ads-mcp-tools.md)  
Assumptions: [../_shared/assumptions.md](../_shared/assumptions.md)

Prerequisite: `mgid-tracking-health-monitor` pass.

---

## Required inputs

| Input | Required | Default |
|---|---|---|
| Scope | Yes | campaign list from simulator scope, or all active when run standalone |
| Date range | Yes | last 7 days |
| Goal | Yes | user or from `get_campaign` bidding |
| Target CPA/ROAS | If needed | `bidding.price` when strategy=cpa, else **user must supply** |

There is no `get_account_goals`.

---

## MCP tools

| Tool | Purpose |
|---|---|
| `get_portfolio_statistics` | **One call** — spend, conversions, CPA and profit for every campaign |
| `list_campaigns` | status, type, limits.dailyLimit, bidding (config) |
| `get_campaign` | limits detail if needed |
| `get_campaign_statistics` | **Only for shortlisted campaigns** — daily spend rows for cap-binding detection |

Rank the whole account from one `get_portfolio_statistics` call, shortlist the
donors and recipients, and only then spend a `get_campaign_statistics` call per
shortlisted campaign to tell a **budget-capped** winner from a **bid-constrained**
one. That distinction needs daily rows; ranking does not.

---

## Donor criteria (all required)

1. Active
2. **ASSUMPTION:** avg daily spend &lt; 85% of `limits.dailyLimit` over lookback
3. KPI on target (CPA ≤ target × 1.1 or ROAS ≥ target × 0.9) from statistics totals
4. ≥ 3 days with spend in lookback
5. Tracking gate pass for this campaign

---

## Recipient criteria — `increase_budget` (all required)

Recommend **daily limit increase** only when the campaign is **budget-capped**, not merely profitable:

1. Active, tracking OK
2. **ASSUMPTION:** spend ≥ 95% of `limits.dailyLimit` on ≥ 5 of last 7 days (from `get_campaign_statistics` daily rows)
3. CPA ≤ target × 1.1 **or** ROAS ≥ target × 0.9 with ≥ 10 conversions in period
4. Daily limit &gt; 0

**Do not** recommend `increase_budget` when avg daily spend &lt; 85% of dailyLimit — budget is not the binding constraint.

## Underutilized budget — `increase_tcpa` instead

When a campaign is **under-spending** (&lt; 85% of dailyLimit) but KPI-positive (CPA on target or ROAS ≥ 100%):

- Recommend **`increase_tcpa`** (`update_campaign` → `bidding.price`), not a budget increase
- **ASSUMPTION:** suggest +5–15% on target CPA; cap single-step change at 15%
- Rationale: bid/target CPA is limiting auction wins, not budget headroom

---

## Budget increase sizing (capped campaigns)

When recommending `increase_budget`:

1. Fetch daily spend rows + `limits.dailyLimit` from `get_campaign`
2. Optional: `run_custom_report` with `dimensions: ["hour"]` and metrics `spent`, `winRate` to see when the cap binds and how much demand is left on the table
3. **Conservative cap:** single-step increase ≤ **50%** of current dailyLimit
4. **Suggested formula (ASSUMPTION):**
   - `utilization = avg_daily_spend / dailyLimit` over capped days only
   - `base_pct = min(50, max(10, round((0.95 - headroom) * 100)))` where `headroom = 1 - utilization`
   - If `winRate` ≥ 70% on capped days, allow upper half of range; if &lt; 50%, use lower half
   - `new_limit = dailyLimit × (1 + chosen_pct / 100)`
5. Impact: `added_spend_daily ≈ (new_limit - dailyLimit) × min(winRate, 0.85)` — mark ASSUMPTION

---

## Matching (donor ↔ recipient pairs)

- Rank recipients by efficiency; pair with top donors
- **ASSUMPTION:** shift ≤ 20% of donor dailyLimit; max 3 pairs per run
- Impact: `conv ≈ shift / recipient_cpa` (range ±25%) — mark ASSUMPTION

---

## Output

**Chat:** summary + numbered list of proposed moves.

**Return:** `Recommendation[]` with `action_type`: `increase_budget`, `increase_tcpa`, or `reallocate_budget`.

---

## Apply

`update_campaign` (write scope) — decrease donor `limits.dailyLimit`, increase recipient.

If write tool unavailable → `blocked_by: ["mcp_tool_unavailable"]`.
