Spreadsheet Auditor
Audit existing Excel spreadsheets and financial models for correctness defects — formula errors, broken references, bad ranges, totals that don't reconcile, and data-quality risks.
spreadsheet-auditor is a portable Agent Skill for Claude and Codex (and a standalone command-line toolkit) that answers one question: "Can I trust this spreadsheet?"
Most spreadsheet tooling helps you build workbooks. This one audits a workbook you already have — often one you inherited — and returns a severity-ranked report of where it is likely wrong, fragile, or inconsistent. It is deterministic-first: bundled Python checks produce candidate findings with exact cell locations and evidence, so you are not eyeballing cells and guessing.
It is intentionally audit-only: it reports defects and optional annotations, but never silently rewrites, reformats, or "fixes" your source workbook.
Demo (60 seconds)
pip install "spreadsheet-auditor[all]"
spreadsheet-auditor --demo --summary
Or against a workbook of your own:
spreadsheet-auditor path/to/workbook.xlsx \
--out audit_report.md \
--json findings.json \
--annotated audit_annotated.xlsx
A complete demo lives in examples/ with a generator
(examples/make_demo.py), the seeded workbook, and
the resulting report/JSON/HTML/annotated outputs ready to inspect.
Why use it
- Pre-send model review — de-risk an LBO/DCF/budget model before it reaches an investment committee or counterparty.
- "Find the error" debugging — a number looks wrong and you need the cell, not a guess.
- Inherited-model trust assessment — you didn't build it; check whether it is trustworthy.
- CI / batch gating — fail a pipeline when a committed workbook has
Critical defects (see
examples/github-actions/). - FP&A and month-end close — catch reconciliation and range mistakes in recurring workbooks.
What it detects
| Category | Checks |
|---|---|
| Formula integrity | live errors (#REF!, #DIV/0!, #VALUE!, #N/A, ...), broken/deleted references, references to blank precedents (off by default), circular references, formula drift across a row/column, IFERROR/IFNA error masking |
| Hardcodes & inputs | numeric literals embedded in formulas, hardcoded plug values inside a formula block |
| Ranges | aggregate ranges that exclude adjacent data (off-by-one), ranges that include subtotal/total rows, inconsistent aggregate range lengths across peers, hidden rows/columns/sheets inside totals |
| Reconciliation | totals that double-count a component (=SUM(B2:B5)+B5), bare SUM totals whose cached value differs from their components, row totals vs column totals that don't cross-foot |
| Logic & structure | volatile/fragile functions (OFFSET, INDIRECT, NOW, RAND, ...), whole-column references |
| Data hygiene | numbers stored as text, leading/trailing whitespace in keys/labels, duplicate lookup keys, merged cells inside data ranges |
| Finance (opt-in HEUR) | balance-sheet balance, sign convention on revenue/expense rows, quarterly period sequencing |
Each finding carries a detection mode (DET deterministic / HEUR heuristic),
an error-confidence level (Defect / Likely defect / Review / Info), a
severity, evidence, and a suggested fix. Full rule list:
references/check_catalog.md.
The seeded-defect benchmark is published at
benchmarks/seeded_defects_matrix.md;
methodology at
references/benchmark_methodology.md.
Precision on real workbooks is measured on 2,729 public forum workbooks
(SpreadsheetBench) and published, per rule with confidence intervals, at
benchmarks/real_world_precision.md:
80% of sampled findings point at something a reviewer would want to look
at (76% for 0.3.0 and 36% for 0.2.0), with 3,434 findings where 0.3.0 had
11,123 and 0.2.0 had 48,281. Recall is
measured on the modified EUSES corpus, real spreadsheets with one injected
formula fault each, at
benchmarks/real_world_recall.md: 77% of
the injected faults are found at the faulty cell. The harness, the labeled
samples, and the labeling protocol live in
benchmarks/corpus/.
Install
pip install spreadsheet-auditor # core + .xlsx/.xlsm/.csv audit
pip install "spreadsheet-auditor[all]" # adds defusedxml, PyYAML
For local development:
git clone https://github.com/petehottelet/spreadsheet-auditor.git
cd spreadsheet-auditor
pip install -e ".[dev]"
spreadsheet-auditor --healthcheck
python -m pytest tests -q
Releases (source archive + Claude/Codex skill zips + SHA-256 checksums) are published from the Releases page.
Run
# Quick look in the terminal
spreadsheet-auditor model.xlsx
# Write artifacts in every supported format
spreadsheet-auditor model.xlsx \
--out report.md \
--json findings.json \
--annotated annotated.xlsx
spreadsheet-auditor model.xlsx --format html --out report.html
spreadsheet-auditor model.xlsx --format sarif --out report.sarif
# CI-friendly: one-screen summary + non-zero exit on High-or-worse
spreadsheet-auditor model.xlsx --summary --fail-on High
# Bundled demo for a 60-second tour
spreadsheet-auditor --demo --summary
See spreadsheet-auditor --help for the full flag reference, including
--strict, --show-suppressed, --quiet, --config, --ignore,
--pin-suppressions, --recalc-timeout, and --healthcheck --json.
Outputs
- Markdown report for human review, grouped by
Confirmed/Likely/Reviewbuckets so the worst items are easy to triage. - HTML report for browser review or sharing with non-technical reviewers (self-contained, no network).
- JSON findings for reruns, CI, and downstream tooling. Validates against
schemas/findings.schema.json. Each finding carries afingerprintbuilt from the flagged cell's content and labels (workbook findings; CSV findings hash their location), which stays the same across runs and inserted rows or columns, for diffing and for fingerprint-based suppression, andmembers, every cell a repeated mistake covers. - SARIF 2.1.0 for GitHub code scanning. See
examples/github-actions/code-scanning.yml. - Annotated workbook copy with comments at finding cells (
--annotated). The source workbook is never modified. The copy is written by openpyxl, which does not carry over drawings, charts, images, or form controls; the audit warns when the source contains them, so keep the original as the master.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Completed; no findings at or above --fail-on |
| 1 | Completed; findings at or above --fail-on |
| 2 | Completed with coverage limitations (only with --strict or --fail-on None) |
| 3 | Healthcheck failed: required dependency missing |
| 4 | Invalid input: bad command-line arguments or config, a workbook, CSV or suppression file that cannot be read, a preflight/security failure, or an output path that cannot be written. stderr says what to fix |
| 5 | Internal error: a bug in the auditor. stderr has the traceback for a bug report |
| 6 | Incomplete: a check failed or timed out, or limits.max_formulas/limits.max_cells skipped part of the workbook |
Benign limitations (no recalculation engine, missing optional packages) do not
fail a normal run. Use --strict to surface them as exit code 2 in CI.
An incomplete audit exits 6 whatever --fail-on says, because a partial run
that found nothing is not a clean workbook. The findings it reports are real;
coverage.incomplete in the JSON (and a banner in the reports) says what was
not checked, and SARIF marks the run executionSuccessful: false. Raise
limits.timeout_seconds, limits.max_formulas or limits.max_cells to
audit a larger workbook in full.
Agent Skill usage
Install the zip from the Releases page for your surface:
- Claude Code: extract
spreadsheet-auditor-claude.zipinto~/.claude/skills/spreadsheet-auditor/(all projects) or a repository's.claude/skills/spreadsheet-auditor/, withSKILL.mddirectly inside that folder. The machine needs Python 3.11+ withopenpyxl. - claude.ai: upload
spreadsheet-auditor-claude.zipas a custom skill in Settings, with code execution enabled. - Claude API: upload the zip through the Skills API and run it with the
code execution tool. That container has no network access, so
openpyxlmust already be installed there. - Codex: use
spreadsheet-auditor-codex.zip, which addsagents/openai.yaml. - Cursor: load the unzipped folder as a Cursor Skill.
Then ask in plain words, for example:
"Audit
Q3_Forecast.xlsxand tell me what's wrong with it. Write the report toaudit_report.mdand produce an annotated copy."
"I inherited this forecast from someone who left. Can I trust the numbers?"
The agent runs the bundled CLI, checks each flagged cell in context with
scripts/context.py, and reports what is actually wrong, with the cells and
the fixes.
Configuration
Optional config file (JSON or YAML) controls scope, materiality, suppression,
performance limits, and which checks fire. Schema:
schemas/config.schema.json.
Example:
scope:
include_sheets: [Budget, Summary]
headline_outputs: [Summary!C1, Summary!C2]
materiality:
absolute: 1000.0
relative: 0.001
finance:
enabled: true # turn on the finance HEUR pack
limits:
max_formulas: 50000
timeout_seconds: 120
max_reported_findings: 200
checks:
IFERROR_MASK: review # warn instead of error
LITERAL_CONSTANT: off
BLANK_PRECEDENT: error # off by default; on for data that must never have gaps
suppressions:
- rule_id: BROKEN_REFERENCE
range: Imports!A1:A100
reason: External feed populated at runtime; cells start empty.
Safety
Spreadsheet files are treated as untrusted input. Macros are inventoried but
never executed, external links are inventoried but never followed,
recalculation runs headless in an isolated profile, and the original workbook
is never overwritten. Details in
SECURITY.md and
references/limitations.md.
Limitations
Excel-specific behavior is approximated via LibreOffice. Value-dependent checks
(TOTAL_MISMATCH, CROSS_FOOT_FAILURE) require either recalculation
(LibreOffice/Calc) or cached values written by Excel. Dynamic arrays, data
tables, Power Query/Data Model, and macros are inventoried but not executed.
Full list:
references/limitations.md.
Google Sheets: export to .xlsx and audit that. There is no native Sheets API
integration. See
references/google_sheets.md for a recipe.
FAQ
Does it fix my spreadsheet? No. It reports defect candidates with suggested fixes; applying changes is a separate, explicit step.
Is this an accounting or audit certification? No. It flags likely defects and review items; it does not certify business, accounting, tax, or legal correctness.
Does it work without Excel installed? Yes. It reads workbooks with
openpyxl. LibreOffice is optional and only used to refresh cached values for
value-dependent checks.
Does it support Google Sheets? Not directly. Export to .xlsx and audit
that.
How are false positives handled? Suppress them in .audit-ignore (or the
config's suppressions), always with a reason:
LIVE_ERROR Model!C5 fingerprint:3f9c2a1b7d4e5f60 accepted: legacy link, FIN-12
BROKEN_REFERENCE Imports raw feed, cells start empty
LITERAL_CONSTANT 'Revenue Detail'!B2:B40 contract uplift rates
To accept one finding, copy the line the Markdown report prints under it (the
first line above). It pins the suppression to the finding's fingerprint, which
is built from the flagged cell's content, its row and column labels, and its
sheet: the line keeps suppressing that finding when rows or columns are
inserted, never hides a different finding that lands on the address, and the
report says when the finding has moved away from the address written in the
line. When two findings of a rule look alike in all of that, the line also
needs its address to match, and adding or fixing one of them turns the line
stale rather than letting it pass to the other. Without a
fingerprint, a target follows its address, which is right for an area such as
a raw-data sheet or an import range; an unpinned one-cell line would also hide
whatever later lands on that cell, so the report prints the pinned line to
replace it with, and --pin-suppressions rewrites those lines in place (and
updates the address of pinned lines whose finding has moved), keeping every
other line of the file. It pins each line to whatever it matches in that run,
so run it before rows move and review the changes it prints. A target may be a cell, a range, a whole column or row, or a
bare sheet name (a target without ! is always a sheet, even Q1); quote
sheet names that contain spaces. A finding is suppressed when its cell lies
inside the target on the same sheet, never by text prefix (Imports!A1 does
not hide Imports!A10). A finding that stands for several cells (a mistake
repeated down a column, listed in its members) is suppressed only when the
target covers all of them; a target covering some is reported with the range
that would cover them all. Suppressions that are malformed, or that match no
finding (fixed, or the cells changed), are called out in the report's coverage
limitations. Suppressed findings stay in the JSON payload (auditable) but are
hidden from the report unless --show-suppressed is passed.
Contributing
We welcome new checks, additional test workbooks, and benchmark improvements.
See CONTRIBUTING.md and
references/custom_checks.md.
License
MIT. See LICENSE.
Topics: spreadsheet audit, excel auditor, xlsx, xlsm, financial model review, formula error checker, reconciliation, cross-foot, range check, circular reference detection, data quality, openpyxl, python, CLI, agent skill, static analysis, claude, codex, FP&A, financial modeling, fpna.
Metadata
Release files for spreadsheet-auditor 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| spreadsheet_auditor-0.4.0.tar.gz | 185.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| spreadsheet_auditor-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 320.5 kB
Release files / spreadsheet_auditor-0.4.0.tar.gz
| Download URL | spreadsheet_auditor-0.4.0.tar.gz |
|---|---|
| Size | 185.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0f03bffa97b7f14c69838b558b0a951984e56a9b279e6370e2edc4aa96c2658b
|
|
BLAKE2b-256 checksum How to use checksums |
5d11ed7783b6470fd9f443f7fd2b314f77d397f803c499b7ed7365bae512014d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / spreadsheet_auditor-0.4.0-py3-none-any.whl
| Download URL | spreadsheet_auditor-0.4.0-py3-none-any.whl |
|---|---|
| Size | 135.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7517fb7058564aeb87cbe89914feb0d252a2e3e036151452c7ce432963f3ece0
|
|
BLAKE2b-256 checksum How to use checksums |
ca9e0efe80de41d5ba895d6f50c5271f7cdfed6892efc4946e1100c7fe746276
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency log