---
name: explain-my-variance
description: "Tell me why a P&L line moved. I compare actuals against budget (if you have one), prior period, and same period prior year, decompose every material swing into price / volume / mix / one-time drivers tied to specific journal entries and vendors, and write a 3–5 paragraph narrative on the biggest movers. Materiality threshold defaults to 5% and $1k and is configurable per run. Unexplained residuals are surfaced, never quietly absorbed. Drafts only — I never re-classify or post to clean up a variance."
version: 1
category: Bookkeeping
featured: no
image: ledger
---


# Explain My Variance

Actuals vs. budget vs. prior period vs. same-period prior year. I decompose every material line into price / volume / mix / one-time drivers, each one tied to specific journal entry ids or transaction sets. The narrative focuses on the 3–5 biggest movers so you read a story, not a spreadsheet. Anything I can't explain, I label as residual rather than make up a cause.

## When to use

- "why was operating expenses up in March" / "what drove revenue miss".
- "compare actuals to budget for {period}".
- "run variance analysis on {period}".
- Called by `close-my-month` after `prepare-my-financials statement=pnl` wrote current-period P&L.

## Connections I need

I run external work through Composio. Before this skill runs I check that the categories below are linked. Missing → I name the category, ask you to connect it from the Integrations tab, stop.

- **No external connections required.** I work entirely from your existing journal entries, P&Ls, and budget file.

This skill never blocks on a missing connection.

## Information I need

I read your bookkeeping context first. For every required field that's missing I ask ONE plain-language question (best modality: connected app > file drop > URL > paste) and wait.

- **The period to analyze** — Required. Why: defines the actuals window and which prior periods to compare against. If missing I ask: "Which period are we running variance on, e.g. March 2025 or Q1 2025?"
- **A finished P&L for the period** — Required. Why: variance compares this period's P&L lines against baselines. If missing I ask: "Have we closed and generated the P&L for that period yet? If not, let's run that first."
- **A chart of accounts** — Required. Why: I group variances by account code line and statement section from your chart of accounts. If missing I ask: "Do we have a chart of accounts yet? If not, let's draft one first."
- **A current budget** — Optional. Why: lets me run actuals vs. budget alongside actuals vs. prior period. If you don't have one I keep going and note "no budget on file" in the report.
- **At least one prior P&L (last month or same month last year)** — Optional but strongly preferred. Why: gives me a baseline to compare against. If you don't have one I report actuals only and flag that there's nothing to compare to yet.

## Steps

1. **Read context.** Load `context/bookkeeping-context.md`, `config/context-ledger.json` (for `domains.budget`  -  cadence + path), `config/chart-of-accounts.json`. Read `config/budget.json` if exists (`[{period, glCode, amount, note?}]`).

2. **Pick comparison baselines.** For requested `period` (`YYYY-MM` or `YYYY-QN`), assemble up to three baselines:
   - **Budget**  -  rows from `config/budget.json` for period. If absent, skip + note "no budget on file".
   - **Prior period**  -  `financials/{prior-YYYY-MM}/pnl.md` if present; else recompute on fly from `journal-entries.json`.
   - **Same period prior year**  -  `financials/{prior-YYYY-MM-12}/pnl.md` if present; else recompute.

3. **Load actuals for period.** Read period P&L from `financials/{YYYY-MM}/pnl.md` (generated by `prepare-my-financials`). If missing, recompute from `journal-entries.json` on fly + note canonical P&L not yet written.

4. **Compute variances per account code line.** For each account code line in any of: actuals, budget, prior period, prior year, compute:
   - `actual_minus_budget`, `pct_vs_budget`
   - `actual_minus_prior_period`, `pct_vs_prior_period`
   - `actual_minus_prior_year`, `pct_vs_prior_year`

5. **Apply materiality threshold.** Default: `abs(variance) > 5% AND abs(variance) > $1000`. Configurable per run via arg. Only material variances get driver decomposition. Non-material summarized in single table at bottom.

6. **Decompose each material variance into drivers.** Ground each driver in specific journal entries or transactions:
   - **Price**  -  same-quantity unit cost changed (e.g., SaaS vendor price increase). Cite journal entry ids where new price first appears.
   - **Volume**  -  more or fewer units at same unit price (e.g., more hosting spend because usage doubled). Cite transaction count vs. baseline + representative journal entry ids.
   - **Mix**  -  different blend of SKUs / vendors / categories. Cite entrant journal entries + exited vendors.
   - **One-time**  -  non-recurring (true-up, one-off refund, annual renewal booked in month). Cite journal entry id + memo.

   Each driver has: `{driver, amount, jeRefs: [id…], transactionRefs?: [ids…], narrative}`. `amount` must sum to total variance within $1.00. Unexplained residual logged explicitly  -  never silently absorbed.

7. **Write plain-English narrative on 3-5 biggest movers.** Narrative names each mover, dollar impact, primary driver, specific evidence (journal entry id or vendor or transaction set). No invented causes. Thin evidence → say so. "no obvious driver, recommend user review" acceptable. Speculation padding not.

8. **Write variance artifact.** Path: `variance-analyses/{YYYY-MM}.md`. Atomic write: `.tmp` → rename. Structure:
   - Header: period, baselines used, materiality threshold, accounting method.
   - **Headline**  -  1-2 sentence summary (e.g., "Operating expenses +$45k (+12%) vs. budget, driven by hosting doubling and a one-time legal true-up").
   - **Narrative**  -  3-5 paragraphs on biggest movers, each citing journal entry ids / vendors / transaction counts.
   - **Material variances table**  -  one row per material account code line with actuals, each baseline, variance, driver decomposition.
   - **Non-material variances**  -  compact summary table.
   - **Unexplained residuals**  -  any driver decomposition not tied back within $1.00.
   - Footer: sources (P&L file path, budget file path, prior-period P&L paths, journal-entries.json hash).

9. **Append to `outputs.json`.** Read-merge-write. Row: `{id, type: "variance-analysis", title: "Variance  -  {YYYY-MM}", summary: "<the headline>", path: "variance-analyses/{YYYY-MM}.md", status: "draft", domain: "reporting"}`.

10. **Summarize to user.** One paragraph: headline + 3-5 biggest movers with dollar impact + primary driver, plus unexplained residuals needing review. Point at written file.

## Outputs

- `variance-analyses/{YYYY-MM}.md`
- `outputs.json` row: `type: "variance-analysis"`, `domain: "reporting"`, `status: "draft"` until user signs off.