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 va...
Scanned 9/19/2026
Install to Claude Code
npx -y skills add gethouston/houston --skill explain-my-variance --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Explain My Variance?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/gethouston-explain-my-variance)More formats (shields.io, HTML) on the badges page.
---
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.Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!