spreadsheet-auditor 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. spreadsheet_auditor/__init__.py +13 -0
  2. spreadsheet_auditor/__main__.py +8 -0
  3. spreadsheet_auditor/annotate.py +48 -0
  4. spreadsheet_auditor/audit.py +761 -0
  5. spreadsheet_auditor/checks/__init__.py +35 -0
  6. spreadsheet_auditor/checks/base.py +74 -0
  7. spreadsheet_auditor/checks/data_hygiene.py +24 -0
  8. spreadsheet_auditor/checks/finance.py +202 -0
  9. spreadsheet_auditor/checks/formula_integrity.py +91 -0
  10. spreadsheet_auditor/checks/ranges.py +58 -0
  11. spreadsheet_auditor/checks/reconciliation.py +32 -0
  12. spreadsheet_auditor/cli.py +17 -0
  13. spreadsheet_auditor/config_loader.py +132 -0
  14. spreadsheet_auditor/data_hygiene.py +115 -0
  15. spreadsheet_auditor/demo/__init__.py +10 -0
  16. spreadsheet_auditor/demo/demo_bad_budget.xlsx +0 -0
  17. spreadsheet_auditor/dependency_graph.py +55 -0
  18. spreadsheet_auditor/finding.py +136 -0
  19. spreadsheet_auditor/formula_drift.py +128 -0
  20. spreadsheet_auditor/formula_parser.py +110 -0
  21. spreadsheet_auditor/materiality.py +22 -0
  22. spreadsheet_auditor/preflight.py +53 -0
  23. spreadsheet_auditor/py.typed +0 -0
  24. spreadsheet_auditor/range_checks.py +299 -0
  25. spreadsheet_auditor/recalc.py +63 -0
  26. spreadsheet_auditor/reconcile.py +130 -0
  27. spreadsheet_auditor/reference_resolver.py +61 -0
  28. spreadsheet_auditor/report.py +399 -0
  29. spreadsheet_auditor/sarif.py +141 -0
  30. spreadsheet_auditor/suppressions.py +109 -0
  31. spreadsheet_auditor/workbook_inventory.py +93 -0
  32. spreadsheet_auditor-0.1.0.dist-info/METADATA +332 -0
  33. spreadsheet_auditor-0.1.0.dist-info/RECORD +37 -0
  34. spreadsheet_auditor-0.1.0.dist-info/WHEEL +5 -0
  35. spreadsheet_auditor-0.1.0.dist-info/entry_points.txt +2 -0
  36. spreadsheet_auditor-0.1.0.dist-info/licenses/LICENSE +21 -0
  37. spreadsheet_auditor-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,13 @@
1
+ """Audit existing spreadsheets for correctness defects.
2
+
3
+ `spreadsheet-auditor` is an audit-only Agent Skill and CLI for reviewing
4
+ existing Excel workbooks and financial models. It identifies formula errors,
5
+ broken references, range mistakes, hardcoded values, reconciliation failures,
6
+ circular references, hidden-structure risks, and data-quality issues without
7
+ modifying the source workbook.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ __version__ = "0.1.0"
13
+ __all__ = ["__version__"]
@@ -0,0 +1,8 @@
1
+ """Enable `python -m spreadsheet_auditor` invocation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ raise SystemExit(main())
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from openpyxl import load_workbook
6
+ from openpyxl.comments import Comment
7
+
8
+
9
+ def _anchor_location(location: str) -> tuple[str, str] | None:
10
+ """Resolve a finding location to a single (sheet, coordinate) anchor cell.
11
+
12
+ Handles comma-joined multi-locations (uses the first) and range locations
13
+ (uses the top-left cell), e.g. "Sheet1!A1:B2" -> ("Sheet1", "A1").
14
+ """
15
+ if not location:
16
+ return None
17
+ first = location.split(",", 1)[0].strip()
18
+ if "!" not in first:
19
+ return None
20
+ sheet_name, coord = first.split("!", 1)
21
+ coord = coord.split(":", 1)[0].replace("$", "").strip()
22
+ if not sheet_name or not coord:
23
+ return None
24
+ return sheet_name, coord
25
+
26
+
27
+ def annotate_workbook(source_path: str | Path, output_path: str | Path, findings: list[dict]) -> None:
28
+ source = Path(source_path)
29
+ keep_vba = source.suffix.lower() == ".xlsm"
30
+ wb = load_workbook(source, keep_vba=keep_vba)
31
+ for finding in findings:
32
+ if finding.get("suppressed"):
33
+ continue
34
+ location = finding.get("location", "")
35
+ anchor = _anchor_location(location)
36
+ if anchor is None:
37
+ continue
38
+ sheet_name, coord = anchor
39
+ if sheet_name not in wb.sheetnames:
40
+ continue
41
+ cell = wb[sheet_name][coord]
42
+ text = (
43
+ f"{finding.get('severity')} {finding.get('rule_id')}\n"
44
+ f"{finding.get('title')}\n"
45
+ f"Fix: {finding.get('suggested_fix')}"
46
+ )
47
+ cell.comment = Comment(text, "Spreadsheet Auditor")
48
+ wb.save(output_path)