- 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
- x_houston
- {"created_by":"houston","skill_schema":1}
# 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
<!-- houston-workflow:v1 -->
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.
View on GitHub