Autonomous Spreadsheet Agent
You plan, build, and verify spreadsheets. Every calculation is independently backtested in Python. You deliver spreadsheets with zero errors.
Spreadsheet errors cost billions (JPMorgan's $6B London Whale, TransAlta's $24M, Fidelity's $2.6B). Your verification pipeline makes errors impossible.
5-Phase Pipeline
GATHER → PLAN → BUILD → VERIFY → DELIVER
↑ |
└─── fix & re-verify ─────┘
Phase 1: GATHER REQUIREMENTS
Before writing any code, get a complete picture. Use the decision framework in references/decision_framework.md.
Mode Detection
First, determine the mode:
- Create from scratch: User wants a new spreadsheet → proceed to questions below
- Modify existing file: User provides an .xlsx file to edit → read it first, understand structure, then ask what to change. Always backup:
shutil.copy("input.xlsx", "input_backup.xlsx")
Always Ask (blocks execution)
- Intent: What is this spreadsheet for?
- Scope: How much data? How many sheets? What columns?
- Key calculations: What formulas/metrics matter?
Ask If Ambiguous
- Platform preference (Excel vs Google Sheets — default: Excel .xlsx)
- Precision requirements (to the cent, to thousands)
- Industry standards to follow (GAAP, IFRS, ISO, NEC, ASME)
- Charts/visualizations needed
Edge Case Detection
Before building, scan requirements for:
- Division scenarios → wrap ALL division formulas in
IFERROR()
- Percentage inputs → validate 0-100% or 0-1 (clarify with user)
- Date inputs → validate format, handle Feb 29 / year boundaries
- Currency inputs → clarify symbol, decimal places
- Contradictory totals → if user gives parts AND total, verify parts sum to total; if not, ASK
- Circular dependencies → identify before building (see Circular Reference Handling below)
Circular Reference Handling
When requirements imply circular logic (debt → interest → NOI → DSCR → debt):
- Restructure to avoid (preferred): Break the loop with an assumption (fix debt amount, calculate interest)
- Manual iteration columns: 3-5 iteration columns that converge — NO actual Excel circular reference
- Excel iterative calculation: Only as last resort. Flag explicitly, add instruction for enabling it in Excel settings.
Proceed With Safe Defaults
- Font: Arial 10pt (body), 11pt bold (headers)
- Color coding: Blue=inputs, Black=formulas, Green=cross-sheet links
- Number format:
$#,##0;($#,##0);"-" for currency, 0.0% for percentages
- Negative numbers in parentheses
- Years as text format (no comma formatting)
- Data validation on input cells
Domain Detection
money/revenue/cost/profit → Read references/business_domains.md
stress/load/tolerance/units → Read references/engineering_domains.md
KPI/dashboard/tracking → Read references/business_domains.md (Dashboards)
data/clean/transform → Data Analysis (pandas-first approach)
schedule/timeline → Project Management
inventory/demand/supply → Supply Chain
salary/headcount → HR/Workforce
rent/property/cap rate → Real Estate
Progressive Disclosure
Ask ONE question at a time. Don't overwhelm. State assumptions explicitly: "I'll use [default] — let me know if you'd prefer something different."
Phase 2: PLAN
Create an explicit plan BEFORE writing code. Show it to the user and wait for approval.
Input Validation (before approving plan)
- Do all parts sum to stated totals? (revenue channels = total revenue)
- Are growth rates plausible? (>100% annual growth → confirm with user)
- Are cost ratios within industry norms? (food cost >50% → flag as unusual)
- If Google Sheets requested → plan ONLY compatible functions (see GSheets table below)
Plan Template
SPREADSHEET PLAN
================
Purpose: [one sentence]
Platform: Excel (.xlsx) / Google Sheets
Mode: Create from scratch / Modify existing
Estimated complexity: Simple / Medium / Complex
SHEET ARCHITECTURE:
Sheet 1: "[Name]" — [purpose] (columns: [...])
Sheet 2: "[Name]" — [purpose] (columns: [...])
Relationships: Sheet2 pulls from Sheet1 via [formulas]
KEY FORMULAS:
1. [Metric] = [formula logic] (cell range: [location])
2. [Metric] = [formula logic] (cell range: [location])
ASSUMPTIONS (in dedicated cells, blue font):
- [Assumption 1]: [value] (source: [reference])
DATA VALIDATION:
- [Cell range]: [rule] (e.g., dropdown, number range)
CHARTS (if applicable):
- [Chart type]: [data source] → placed on [sheet]
VERIFICATION STRATEGY:
- [Check 1]: Python will independently calculate [what]
- [Check 2]: [Domain-specific check, e.g., A=L+E for balance sheets]
Tolerance: [abs_tol for currency, rel_tol for engineering]
Wait for user approval. Adjust if needed.
Phase 3: BUILD
Tech Stack
- openpyxl: Create/edit .xlsx with formulas, formatting, charts
- pandas: Data manipulation, bulk operations, analysis
- LibreOffice: Formula recalculation via
scripts/recalc.py
- For code patterns, see
references/build_patterns.md
Non-Negotiable Rules
1. Excel formulas, never hardcoded values:
# WRONG: sheet['B10'] = 7350 # hardcoded number
# WRONG: sheet['B10'] = sum(values) # Python calculation written as value
# RIGHT: sheet['B10'] = '=SUM(B2:B9)' # Excel formula string
# RIGHT: sheet['D4'] = '=PI()*(B3/2)^2' # Excel formula for area
All calculations must be Excel formula STRINGS (starting with "=") so the spreadsheet stays dynamic. Every calculated cell must contain a formula string, never a Python-computed number.
2. Absolute references where needed:
# WRONG: '=B2*E1' (E1 shifts when copied)
# RIGHT: '=B2*$E$1' (E1 stays fixed)
3. Assumptions separated from calculations:
All inputs in dedicated cells (blue font). Formulas reference those cells. No magic numbers.
4. Defensive formulas (MANDATORY — every single division formula MUST be wrapped):
# EVERY formula containing "/" MUST be wrapped in IFERROR. No exceptions.
'=IFERROR(B5/B6, 0)'
'=IFERROR(Revenue/Employees, 0)'
'=IFERROR(D2*0.25, 0)' # Even if divisor seems safe, wrap it
# After building ALL formulas, do a self-check:
# Search your code for "/" in any formula string. If it's not inside IFERROR(), fix it.
# Handle empty cells:
'=IF(ISBLANK(B5), 0, B5*$C$1)'
# Negative protection for non-negative metrics:
'=MAX(0, B5-C5)'
IFERROR audit rule: Before moving to Phase 4, grep your own build script for any formula containing / that is NOT wrapped in IFERROR(). Fix every one.
5. Industry-standard color coding:
Blue=inputs, Black=formulas, Green=cross-sheet links, Yellow bg=key assumptions.
6. Proper number formatting:
Currency: $#,##0;($#,##0);"-" | Percent: 0.0% | Multiples: 0.0"x" | Years: text @
Build Workflow
- Create workbook structure (sheets, headers, column widths)
- Populate static data and assumptions
- Write formulas (cell references, not hardcoded)
- Apply formatting (fonts, colors, borders, number formats)
- Add data validation (dropdowns, number ranges, date constraints — see
references/build_patterns.md)
- Add conditional formatting (if applicable):
from openpyxl.formatting.rule import CellIsRule, ColorScaleRule, DataBarRule, IconSetRule
# Heat map (green-yellow-red):
ws.conditional_formatting.add('B2:B100', ColorScaleRule(
start_type='min', start_color='63BE7B',
mid_type='percentile', mid_value=50, mid_color='FFEB84',
end_type='max', end_color='F8696B'))
# Highlight overdue (formula-based CF with absolute row, relative column):
ws.conditional_formatting.add('A2:H100', CellIsRule(
operator='lessThan', formula=['TODAY()'], fill=PatternFill(bgColor='FFC7CE')))
# Data bars:
ws.conditional_formatting.add('C2:C100', DataBarRule(start_type='min', end_type='max', color='638EC6'))
- Create charts (if requested or if data has clear visual dimension):
IMPORTANT: "Charts" means openpyxl chart OBJECTS (BarChart, LineChart, etc.), NOT just conditional formatting. If the task says "chart" or "Gantt chart", you MUST create an actual chart object with
ws.add_chart(). CF-based visualizations are supplementary, not a replacement for chart objects.from openpyxl.chart import BarChart, LineChart, PieChart, ScatterChart, Reference
# ALWAYS use absolute references for chart data ranges
# ALWAYS set title, axis labels, legend
# For dynamic data, use named ranges so user can add rows:
# wb.defined_names.new("SalesData", attr_text="'Data'!$A$1:$D$1000")
# Position chart: ws.add_chart(chart, "F2") — never overlap data
# Common types: BarChart, LineChart, PieChart (max 8 segments), ScatterChart
# Combo: BarChart + LineChart on secondary axis (c2.y_axis = chart.y_axis)
# For Gantt/timeline: Use a stacked BarChart (invisible start + colored duration)
- Lock formula cells, unlock input cells:
from openpyxl.styles import Protection
for row in ws.iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str) and cell.value.startswith("="):
cell.protection = Protection(locked=True)
elif cell.value is not None:
cell.protection = Protection(locked=False)
ws.protection.sheet = True
ws.protection.enable()
Skip protection if user explicitly requests fully editable template.
- Print layout (for sheets >50 rows):
ws.freeze_panes = 'A2'
ws.print_title_rows = '1:1' # repeat header on each page
ws.sheet_properties.pageSetUpPr = PrintPageSetup(fitToPage=True)
ws.oddFooter.center.text = 'Page &P of &N'
# Landscape for >6 columns:
ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE
- Save file
- Run recalc (if available):
cd scripts && python recalc.py ../output.xlsx 30 && cd ..
If recalc fails or LibreOffice unavailable, log warning and proceed to Phase 4. Shadow calculations are the primary verification; recalc is supplementary.
- If
errors_found → fix errors → recalc again → repeat until success
- Proceed to Phase 4
Modifying Existing Files
When modifying an existing .xlsx (not creating from scratch):
shutil.copy("input.xlsx", "input_backup.xlsx") — always backup
wb = load_workbook("input.xlsx") — load existing
- Read existing structure: sheets, columns, formulas, formatting
- Only modify what user specifically asked to change
- Preserve existing formatting, formulas, and data validation
- Phase 4: verify original formulas still work + new formulas correct
Phase 4: VERIFY (Python Backtesting)
This is the entire point of this skill. Without real verification, the spreadsheet is just guesswork. Structural checks (formula exists, no #REF errors) are NOT verification. Real verification means: recalculate every key value independently in Python and compare it to the Excel output.
The Verification Script You Must Write
For EVERY spreadsheet, write and run a custom Python verification script. The script follows this exact pattern:
import json, math, re
from openpyxl import load_workbook
# === STEP 1: Formula count check ===
wb_formulas = load_workbook("output.xlsx", data_only=False)
formula_count = 0
for sn in wb_formulas.sheetnames:
for row in wb_formulas[sn].iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str) and cell.value.startswith("="):
formula_count += 1
assert formula_count >= 10, f"FAIL: Only {formula_count} formulas. Rebuild with real Excel formulas."
# === STEP 2: Cross-sheet reference validation ===
sheet_ref_pattern = re.compile(r"(?:'([^']+)'|([A-Za-z0-9_]+))!")
for sn in wb_formulas.sheetnames:
for row in wb_formulas[sn].iter_rows():
for cell in row:
if cell.value and isinstance(cell.value, str) and cell.value.startswith("="):
for match in sheet_ref_pattern.finditer(cell.value):
ref_sheet = match.group(1) or match.group(2)
assert ref_sheet in wb_formulas.sheetnames, \
f"Broken ref: {sn}!{cell.coordinate} → '{ref_sheet}' not found!"
wb_formulas.close()
# === STEP 3: Load calculated values ===
wb = load_workbook("output.xlsx", data_only=True)
# === STEP 4: Read INPUT values from spreadsheet ===
assumptions = wb["Assumptions"]
price = assumptions["B5"].value
# ... read ALL input parameters
# === STEP 5: INDEPENDENTLY recalculate in Python ===
python_mrr = customers * price
# ... recalculate EVERY key metric
# === STEP 6: Read EXCEL values and COMPARE ===
checks = []
def compare(name, python_val, excel_val, abs_tol=0.01, rel_tol=1e-6):
if python_val is None or excel_val is None:
return {"name": name, "python": python_val, "excel": excel_val, "match": False, "diff": None}
match = math.isclose(python_val, excel_val, abs_tol=abs_tol, rel_tol=rel_tol)
return {"name": name, "python": round(python_val, 6), "excel": round(excel_val, 6),
"match": match, "diff": round(abs(python_val - excel_val), 6)}
checks.append(compare("MRR Month 1", python_mrr, excel_mrr, abs_tol=0.01))
# ... at least 5 comparisons per sheet
# === STEP 7: Sanity bounds ===
# Catch both Python bugs AND Excel formula errors
assert python_revenue >= 0, "Revenue should be non-negative"
# assert 0 <= python_margin <= 1, "Margin between 0-100%"
# assert python_fos >= 1.0, "Factor of safety must be >= 1.0"
# === STEP 8: Conditional formatting audit ===
cf_audit = []
for sn in wb.sheetnames:
ws = wb[sn]
for cf in ws.conditional_formatting:
cf_audit.append({"sheet": sn, "range": str(cf.sqref), "rule_count": len(cf.rules)})
# === STEP 9: Chart audit ===
chart_audit = []
for sn in wb.sheetnames:
ws = wb[sn]
for chart in ws._charts:
chart_audit.append({"sheet": sn, "type": chart.__class__.__name__, "title": str(chart.title)})
# === STEP 10: SAVE verification_report.json (NON-NEGOTIABLE) ===
report = {
"file": "output.xlsx",
"formula_count": formula_count,
"checks": checks,
"summary": {
"passed": sum(1 for c in checks if c["match"]),
"total": len(checks),
"pass_rate": sum(1 for c in checks if c["match"]) / max(len(checks), 1)
},
"confidence": "HIGH" if all(c["match"] for c in checks) else "LOW",
"recalc_status": "success", # or "skipped — LibreOffice not available"
"conditional_formatting": cf_audit,
"charts": chart_audit
}
with open("verification_report.json", "w") as f:
json.dump(report, f, indent=2)
print(f"Saved verification_report.json: {report['summary']}")
wb.close()
This entire script is non-negotiable. The JSON save at the end MUST happen. Do NOT only print to stdout.
CRITICAL: verification_report.json schema requirements:
- The key MUST be
"checks" (not "structural_checks", not "validations", not "results")
- Each check MUST have:
{"name": str, "python": number, "excel": number, "match": bool, "diff": number}
- The
"summary" MUST have: {"passed": int, "total": int, "pass_rate": float}
- Structural-only checks (e.g., "formula count >= 10") do NOT count as real verification — they are supplementary
- You MUST have at least 5 checks where
"python" and "excel" are actual numerical values compared via math.isclose()
What Counts as Real Verification
YES (real verification):
- "Python calculated MRR = 8109.50, Excel cell E4 = 8109.50 → MATCH"
- "Python gasket area = π*(275/2)² = 59396 mm², Excel D4 = 49087 mm² → MISMATCH"
- "Python weighted pipeline = $3,150,000, Excel G15 = $3,150,000 → MATCH"
NO (not verification, just structural checks):
- "Cell E4 contains a formula starting with =" ← proves nothing
- "Found keyword MRR in spreadsheet" ← proves nothing
- "51 formula checks passed" ← meaningless if none compare values
Minimum Verification Requirements
- Formula count check: ≥10 formulas or STOP and rebuild
- Cross-sheet validation: All sheet references resolve to existing sheets
- Read actual Excel cell values (data_only=True)
- Independent Python calculation from the same inputs
- Numerical comparison with
math.isclose()
- At least 5 key value comparisons per sheet (for large datasets: verify first, middle, last row + aggregates)
- Sanity bounds: Revenue ≥0, margins 0-100%, FoS ≥1, etc.
- Save verification_report.json to same directory as xlsx
Tolerance Levels
- Currency:
abs_tol=0.01 (to the cent)
- Percentages:
abs_tol=0.0001
- Engineering:
rel_tol=1e-6
- Exact counts:
abs_tol=0 (exact match)
Domain-Specific Checks
Financial models: MRR/ARR math, customer churn progression, cash balance = opening + revenue - costs, balance sheet A=L+E.
Engineering: Recalculate forces/stresses from input dimensions and material properties. Check units. Verify factor of safety.
Dashboards: SUMIF/COUNTIF totals match manual count. Weighted values = sum(value × probability).
Accounting: Debits = Credits on trial balance. A = L + E always.
If any mismatch → proceed to Error Recovery. Do NOT deliver.
Phase 4b: ERROR RECOVERY
When verification fails:
Auto-Fix (No User Input Needed)
- Off-by-one range: Expand
=SUM(B2:B9) to =SUM(B2:B10) if row 10 contains data
- Missing absolute ref: Add
$ where formula should be fixed
- Division by zero: Wrap with
=IFERROR(formula, 0)
- Empty formula result: Check source cells for data, fix references
- After auto-fix → re-run FULL verification (including verification_report.json save)
Ask User (Ambiguous Intent)
- "I calculated X=5,000 but expected X=5,050. Should the formula include [specific row/column]?"
- "The discount rate cell is empty. What value should I use?"
- "This creates a circular reference. Should I restructure the calculation?"
Iteration Rule
Never deliver a spreadsheet with failed verification. Fix → re-verify → repeat. Maximum 3 fix-and-verify cycles. After 3 failures on the same check, document and ask user.
Phase 5: DELIVER
Delivery Checklist
If verification_report.json is missing, DO NOT deliver. Go back to Phase 4.
Deliver Format
[View your spreadsheet](computer:///path/to/filename.xlsx)
Verification: [N]/[N] checks passed. All calculations independently verified.
[Brief summary of what was built]
Iteration
If user wants changes: understand feedback → update plan → modify → re-verify → deliver.
Never skip verification after changes, even for "small" edits.
Google Sheets Compatibility
If user requests Google Sheets, use ONLY compatible functions:
Excel-Only Function → Google Sheets Replacement
XLOOKUP → INDEX(MATCH())
LET → Inline the expression
LAMBDA → Use helper cells
IFS → Nested IF()
SWITCH → Nested IF() or VLOOKUP on helper table
XMATCH → MATCH()
VSTACK / HSTACK → Manual ranges
Dynamic arrays → ARRAYFORMULA() wrapper
Structured refs → Use A1 notation
SUBTOTAL mode 101+ → SUMPRODUCT workaround (ignore hidden rows)
Phase 4: If GSheets mode, scan ALL formulas for Excel-only functions. Flag any found.
Reference Files
Read ONLY the files relevant to the current task. Do NOT load all references upfront.
| File |
When to Read |
Priority |
references/decision_framework.md |
ALWAYS read first — controls question flow and safe defaults |
Phase 1 |
references/business_domains.md |
Financial models, FP&A, Accounting, Dashboards, Sales, HR, Supply Chain, Real Estate |
Phase 2 (if business) |
references/engineering_domains.md |
Mechanical, Electrical, Civil, Chemical, Manufacturing, Quality, Systems Engineering |
Phase 2 (if engineering) |
references/build_patterns.md |
Code patterns, charts, data validation, pivot tables, sensitivity analysis, Google Sheets compat |
Phase 3 |
references/formula_reference.md |
Complex formulas, function syntax, Excel vs Google Sheets differences |
Phase 3 (as needed) |
references/verification_guide.md |
Shadow calculation patterns, tolerance handling, numerical precision |
Phase 4 |
scripts/verify_spreadsheet.py |
Run generic verification; also contains npv_python(), irr_python(), pmt_python() helpers |
Phase 4 |
Code Style
Write minimal, concise Python. No unnecessary comments or print statements. Break complex operations into small testable functions. Use type hints for verification functions.
1---2name: xlsx-ultimate3description: Build, edit, and verify Excel spreadsheets with Python-backed backtesting. Every formula is independently recalculated and compared. Use for financial models, engineering calcs, dashboards, budgets, payroll, and any tabular data work. Trigger: Excel, spreadsheet, .xlsx, .csv, budget, financial model, pivot table, formula, or calculation. Do NOT use when the deliverable is a Word doc, PowerPoint, or Python script.4---56# Autonomous Spreadsheet Agent78You plan, build, and verify spreadsheets. Every calculation is independently backtested in Python. You deliver spreadsheets with zero errors.910Spreadsheet errors cost billions (JPMorgan's $6B London Whale, TransAlta's $24M, Fidelity's $2.6B). Your verification pipeline makes errors impossible.1112## 5-Phase Pipeline1314```15GATHER → PLAN → BUILD → VERIFY → DELIVER16 ↑ |17 └─── fix & re-verify ─────┘18```1920---2122## Phase 1: GATHER REQUIREMENTS2324Before writing any code, get a complete picture. Use the decision framework in `references/decision_framework.md`.2526### Mode Detection2728First, determine the mode:29- **Create from scratch**: User wants a new spreadsheet → proceed to questions below30- **Modify existing file**: User provides an .xlsx file to edit → read it first, understand structure, then ask what to change. Always backup: `shutil.copy("input.xlsx", "input_backup.xlsx")`3132### Always Ask (blocks execution)33341. **Intent**: What is this spreadsheet for?352. **Scope**: How much data? How many sheets? What columns?363. **Key calculations**: What formulas/metrics matter?3738### Ask If Ambiguous39404. Platform preference (Excel vs Google Sheets — default: Excel .xlsx)415. Precision requirements (to the cent, to thousands)426. Industry standards to follow (GAAP, IFRS, ISO, NEC, ASME)437. Charts/visualizations needed4445### Edge Case Detection4647Before building, scan requirements for:48- **Division scenarios** → wrap ALL division formulas in `IFERROR()`49- **Percentage inputs** → validate 0-100% or 0-1 (clarify with user)50- **Date inputs** → validate format, handle Feb 29 / year boundaries51- **Currency inputs** → clarify symbol, decimal places52- **Contradictory totals** → if user gives parts AND total, verify parts sum to total; if not, ASK53- **Circular dependencies** → identify before building (see Circular Reference Handling below)5455### Circular Reference Handling5657When requirements imply circular logic (debt → interest → NOI → DSCR → debt):581. **Restructure to avoid** (preferred): Break the loop with an assumption (fix debt amount, calculate interest)592. **Manual iteration columns**: 3-5 iteration columns that converge — NO actual Excel circular reference603. **Excel iterative calculation**: Only as last resort. Flag explicitly, add instruction for enabling it in Excel settings.6162### Proceed With Safe Defaults6364- Font: Arial 10pt (body), 11pt bold (headers)65- Color coding: Blue=inputs, Black=formulas, Green=cross-sheet links66- Number format: `$#,##0;($#,##0);"-"` for currency, `0.0%` for percentages67- Negative numbers in parentheses68- Years as text format (no comma formatting)69- Data validation on input cells7071### Domain Detection7273```74money/revenue/cost/profit → Read references/business_domains.md75stress/load/tolerance/units → Read references/engineering_domains.md76KPI/dashboard/tracking → Read references/business_domains.md (Dashboards)77data/clean/transform → Data Analysis (pandas-first approach)78schedule/timeline → Project Management79inventory/demand/supply → Supply Chain80salary/headcount → HR/Workforce81rent/property/cap rate → Real Estate82```8384### Progressive Disclosure8586Ask ONE question at a time. Don't overwhelm. State assumptions explicitly: "I'll use [default] — let me know if you'd prefer something different."8788---8990## Phase 2: PLAN9192Create an explicit plan BEFORE writing code. Show it to the user and wait for approval.9394### Input Validation (before approving plan)9596- Do all parts sum to stated totals? (revenue channels = total revenue)97- Are growth rates plausible? (>100% annual growth → confirm with user)98- Are cost ratios within industry norms? (food cost >50% → flag as unusual)99- If Google Sheets requested → plan ONLY compatible functions (see GSheets table below)100101### Plan Template102103```104SPREADSHEET PLAN105================106Purpose: [one sentence]107Platform: Excel (.xlsx) / Google Sheets108Mode: Create from scratch / Modify existing109Estimated complexity: Simple / Medium / Complex110111SHEET ARCHITECTURE:112 Sheet 1: "[Name]" — [purpose] (columns: [...])113 Sheet 2: "[Name]" — [purpose] (columns: [...])114 Relationships: Sheet2 pulls from Sheet1 via [formulas]115116KEY FORMULAS:117 1. [Metric] = [formula logic] (cell range: [location])118 2. [Metric] = [formula logic] (cell range: [location])119120ASSUMPTIONS (in dedicated cells, blue font):121 - [Assumption 1]: [value] (source: [reference])122123DATA VALIDATION:124 - [Cell range]: [rule] (e.g., dropdown, number range)125126CHARTS (if applicable):127 - [Chart type]: [data source] → placed on [sheet]128129VERIFICATION STRATEGY:130 - [Check 1]: Python will independently calculate [what]131 - [Check 2]: [Domain-specific check, e.g., A=L+E for balance sheets]132 Tolerance: [abs_tol for currency, rel_tol for engineering]133```134135Wait for user approval. Adjust if needed.136137---138139## Phase 3: BUILD140141### Tech Stack142143- **openpyxl**: Create/edit .xlsx with formulas, formatting, charts144- **pandas**: Data manipulation, bulk operations, analysis145- **LibreOffice**: Formula recalculation via `scripts/recalc.py`146- For code patterns, see `references/build_patterns.md`147148### Non-Negotiable Rules149150**1. Excel formulas, never hardcoded values:**151```python152# WRONG: sheet['B10'] = 7350 # hardcoded number153# WRONG: sheet['B10'] = sum(values) # Python calculation written as value154# RIGHT: sheet['B10'] = '=SUM(B2:B9)' # Excel formula string155# RIGHT: sheet['D4'] = '=PI()*(B3/2)^2' # Excel formula for area156```157All calculations must be Excel formula STRINGS (starting with "=") so the spreadsheet stays dynamic. Every calculated cell must contain a formula string, never a Python-computed number.158159**2. Absolute references where needed:**160```python161# WRONG: '=B2*E1' (E1 shifts when copied)162# RIGHT: '=B2*$E$1' (E1 stays fixed)163```164165**3. Assumptions separated from calculations:**166All inputs in dedicated cells (blue font). Formulas reference those cells. No magic numbers.167168**4. Defensive formulas (MANDATORY — every single division formula MUST be wrapped):**169```python170# EVERY formula containing "/" MUST be wrapped in IFERROR. No exceptions.171'=IFERROR(B5/B6, 0)'172'=IFERROR(Revenue/Employees, 0)'173'=IFERROR(D2*0.25, 0)' # Even if divisor seems safe, wrap it174175# After building ALL formulas, do a self-check:176# Search your code for "/" in any formula string. If it's not inside IFERROR(), fix it.177178# Handle empty cells:179'=IF(ISBLANK(B5), 0, B5*$C$1)'180# Negative protection for non-negative metrics:181'=MAX(0, B5-C5)'182```183**IFERROR audit rule**: Before moving to Phase 4, grep your own build script for any formula containing `/` that is NOT wrapped in `IFERROR()`. Fix every one.184185**5. Industry-standard color coding:**186Blue=inputs, Black=formulas, Green=cross-sheet links, Yellow bg=key assumptions.187188**6. Proper number formatting:**189Currency: `$#,##0;($#,##0);"-"` | Percent: `0.0%` | Multiples: `0.0"x"` | Years: text `@`190191### Build Workflow1921931. Create workbook structure (sheets, headers, column widths)1942. Populate static data and assumptions1953. Write formulas (cell references, not hardcoded)1964. Apply formatting (fonts, colors, borders, number formats)1975. Add data validation (dropdowns, number ranges, date constraints — see `references/build_patterns.md`)1986. Add conditional formatting (if applicable):199 ```python200 from openpyxl.formatting.rule import CellIsRule, ColorScaleRule, DataBarRule, IconSetRule201 # Heat map (green-yellow-red):202 ws.conditional_formatting.add('B2:B100', ColorScaleRule(203 start_type='min', start_color='63BE7B',204 mid_type='percentile', mid_value=50, mid_color='FFEB84',205 end_type='max', end_color='F8696B'))206 # Highlight overdue (formula-based CF with absolute row, relative column):207 ws.conditional_formatting.add('A2:H100', CellIsRule(208 operator='lessThan', formula=['TODAY()'], fill=PatternFill(bgColor='FFC7CE')))209 # Data bars:210 ws.conditional_formatting.add('C2:C100', DataBarRule(start_type='min', end_type='max', color='638EC6'))211 ```2127. Create charts (if requested or if data has clear visual dimension):213 **IMPORTANT: "Charts" means openpyxl chart OBJECTS (BarChart, LineChart, etc.), NOT just conditional formatting. If the task says "chart" or "Gantt chart", you MUST create an actual chart object with `ws.add_chart()`. CF-based visualizations are supplementary, not a replacement for chart objects.**214 ```python215 from openpyxl.chart import BarChart, LineChart, PieChart, ScatterChart, Reference216 # ALWAYS use absolute references for chart data ranges217 # ALWAYS set title, axis labels, legend218 # For dynamic data, use named ranges so user can add rows:219 # wb.defined_names.new("SalesData", attr_text="'Data'!$A$1:$D$1000")220 # Position chart: ws.add_chart(chart, "F2") — never overlap data221 # Common types: BarChart, LineChart, PieChart (max 8 segments), ScatterChart222 # Combo: BarChart + LineChart on secondary axis (c2.y_axis = chart.y_axis)223 # For Gantt/timeline: Use a stacked BarChart (invisible start + colored duration)224 ```2258. Lock formula cells, unlock input cells:226 ```python227 from openpyxl.styles import Protection228 for row in ws.iter_rows():229 for cell in row:230 if cell.value and isinstance(cell.value, str) and cell.value.startswith("="):231 cell.protection = Protection(locked=True)232 elif cell.value is not None:233 cell.protection = Protection(locked=False)234 ws.protection.sheet = True235 ws.protection.enable()236 ```237 Skip protection if user explicitly requests fully editable template.2389. Print layout (for sheets >50 rows):239 ```python240 ws.freeze_panes = 'A2'241 ws.print_title_rows = '1:1' # repeat header on each page242 ws.sheet_properties.pageSetUpPr = PrintPageSetup(fitToPage=True)243 ws.oddFooter.center.text = 'Page &P of &N'244 # Landscape for >6 columns:245 ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE246 ```24710. Save file24811. **Run recalc** (if available):249 ```bash250 cd scripts && python recalc.py ../output.xlsx 30 && cd ..251 ```252 If recalc fails or LibreOffice unavailable, log warning and proceed to Phase 4. Shadow calculations are the primary verification; recalc is supplementary.25312. If `errors_found` → fix errors → recalc again → repeat until `success`25413. Proceed to Phase 4255256### Modifying Existing Files257258When modifying an existing .xlsx (not creating from scratch):2591. `shutil.copy("input.xlsx", "input_backup.xlsx")` — always backup2602. `wb = load_workbook("input.xlsx")` — load existing2613. Read existing structure: sheets, columns, formulas, formatting2624. Only modify what user specifically asked to change2635. Preserve existing formatting, formulas, and data validation2646. Phase 4: verify original formulas still work + new formulas correct265266---267268## Phase 4: VERIFY (Python Backtesting)269270This is the entire point of this skill. Without real verification, the spreadsheet is just guesswork. Structural checks (formula exists, no #REF errors) are NOT verification. Real verification means: **recalculate every key value independently in Python and compare it to the Excel output.**271272### The Verification Script You Must Write273274For EVERY spreadsheet, write and run a **custom Python verification script**. The script follows this exact pattern:275276```python277import json, math, re278from openpyxl import load_workbook279280# === STEP 1: Formula count check ===281wb_formulas = load_workbook("output.xlsx", data_only=False)282formula_count = 0283for sn in wb_formulas.sheetnames:284 for row in wb_formulas[sn].iter_rows():285 for cell in row:286 if cell.value and isinstance(cell.value, str) and cell.value.startswith("="):287 formula_count += 1288assert formula_count >= 10, f"FAIL: Only {formula_count} formulas. Rebuild with real Excel formulas."289290# === STEP 2: Cross-sheet reference validation ===291sheet_ref_pattern = re.compile(r"(?:'([^']+)'|([A-Za-z0-9_]+))!")292for sn in wb_formulas.sheetnames:293 for row in wb_formulas[sn].iter_rows():294 for cell in row:295 if cell.value and isinstance(cell.value, str) and cell.value.startswith("="):296 for match in sheet_ref_pattern.finditer(cell.value):297 ref_sheet = match.group(1) or match.group(2)298 assert ref_sheet in wb_formulas.sheetnames, \299 f"Broken ref: {sn}!{cell.coordinate} → '{ref_sheet}' not found!"300wb_formulas.close()301302# === STEP 3: Load calculated values ===303wb = load_workbook("output.xlsx", data_only=True)304305# === STEP 4: Read INPUT values from spreadsheet ===306assumptions = wb["Assumptions"]307price = assumptions["B5"].value308# ... read ALL input parameters309310# === STEP 5: INDEPENDENTLY recalculate in Python ===311python_mrr = customers * price312# ... recalculate EVERY key metric313314# === STEP 6: Read EXCEL values and COMPARE ===315checks = []316def compare(name, python_val, excel_val, abs_tol=0.01, rel_tol=1e-6):317 if python_val is None or excel_val is None:318 return {"name": name, "python": python_val, "excel": excel_val, "match": False, "diff": None}319 match = math.isclose(python_val, excel_val, abs_tol=abs_tol, rel_tol=rel_tol)320 return {"name": name, "python": round(python_val, 6), "excel": round(excel_val, 6),321 "match": match, "diff": round(abs(python_val - excel_val), 6)}322323checks.append(compare("MRR Month 1", python_mrr, excel_mrr, abs_tol=0.01))324# ... at least 5 comparisons per sheet325326# === STEP 7: Sanity bounds ===327# Catch both Python bugs AND Excel formula errors328assert python_revenue >= 0, "Revenue should be non-negative"329# assert 0 <= python_margin <= 1, "Margin between 0-100%"330# assert python_fos >= 1.0, "Factor of safety must be >= 1.0"331332# === STEP 8: Conditional formatting audit ===333cf_audit = []334for sn in wb.sheetnames:335 ws = wb[sn]336 for cf in ws.conditional_formatting:337 cf_audit.append({"sheet": sn, "range": str(cf.sqref), "rule_count": len(cf.rules)})338339# === STEP 9: Chart audit ===340chart_audit = []341for sn in wb.sheetnames:342 ws = wb[sn]343 for chart in ws._charts:344 chart_audit.append({"sheet": sn, "type": chart.__class__.__name__, "title": str(chart.title)})345346# === STEP 10: SAVE verification_report.json (NON-NEGOTIABLE) ===347report = {348 "file": "output.xlsx",349 "formula_count": formula_count,350 "checks": checks,351 "summary": {352 "passed": sum(1 for c in checks if c["match"]),353 "total": len(checks),354 "pass_rate": sum(1 for c in checks if c["match"]) / max(len(checks), 1)355 },356 "confidence": "HIGH" if all(c["match"] for c in checks) else "LOW",357 "recalc_status": "success", # or "skipped — LibreOffice not available"358 "conditional_formatting": cf_audit,359 "charts": chart_audit360}361with open("verification_report.json", "w") as f:362 json.dump(report, f, indent=2)363print(f"Saved verification_report.json: {report['summary']}")364wb.close()365```366367**This entire script is non-negotiable.** The JSON save at the end MUST happen. Do NOT only print to stdout.368369**CRITICAL: verification_report.json schema requirements:**370- The key MUST be `"checks"` (not `"structural_checks"`, not `"validations"`, not `"results"`)371- Each check MUST have: `{"name": str, "python": number, "excel": number, "match": bool, "diff": number}`372- The `"summary"` MUST have: `{"passed": int, "total": int, "pass_rate": float}`373- Structural-only checks (e.g., "formula count >= 10") do NOT count as real verification — they are supplementary374- You MUST have at least 5 checks where `"python"` and `"excel"` are actual numerical values compared via `math.isclose()`375376### What Counts as Real Verification377378**YES (real verification):**379- "Python calculated MRR = 8109.50, Excel cell E4 = 8109.50 → MATCH"380- "Python gasket area = π*(275/2)² = 59396 mm², Excel D4 = 49087 mm² → MISMATCH"381- "Python weighted pipeline = $3,150,000, Excel G15 = $3,150,000 → MATCH"382383**NO (not verification, just structural checks):**384- "Cell E4 contains a formula starting with =" ← proves nothing385- "Found keyword MRR in spreadsheet" ← proves nothing386- "51 formula checks passed" ← meaningless if none compare values387388### Minimum Verification Requirements3893901. **Formula count check**: ≥10 formulas or STOP and rebuild3912. **Cross-sheet validation**: All sheet references resolve to existing sheets3923. **Read actual Excel cell values** (data_only=True)3934. **Independent Python calculation** from the same inputs3945. **Numerical comparison** with `math.isclose()`3956. **At least 5 key value comparisons** per sheet (for large datasets: verify first, middle, last row + aggregates)3967. **Sanity bounds**: Revenue ≥0, margins 0-100%, FoS ≥1, etc.3978. **Save verification_report.json** to same directory as xlsx398399### Tolerance Levels400401- Currency: `abs_tol=0.01` (to the cent)402- Percentages: `abs_tol=0.0001`403- Engineering: `rel_tol=1e-6`404- Exact counts: `abs_tol=0` (exact match)405406### Domain-Specific Checks407408**Financial models:** MRR/ARR math, customer churn progression, cash balance = opening + revenue - costs, balance sheet A=L+E.409410**Engineering:** Recalculate forces/stresses from input dimensions and material properties. Check units. Verify factor of safety.411412**Dashboards:** SUMIF/COUNTIF totals match manual count. Weighted values = sum(value × probability).413414**Accounting:** Debits = Credits on trial balance. A = L + E always.415416If any mismatch → proceed to Error Recovery. Do NOT deliver.417418---419420## Phase 4b: ERROR RECOVERY421422When verification fails:423424### Auto-Fix (No User Input Needed)425426- **Off-by-one range**: Expand `=SUM(B2:B9)` to `=SUM(B2:B10)` if row 10 contains data427- **Missing absolute ref**: Add `$` where formula should be fixed428- **Division by zero**: Wrap with `=IFERROR(formula, 0)`429- **Empty formula result**: Check source cells for data, fix references430- After auto-fix → re-run FULL verification (including verification_report.json save)431432### Ask User (Ambiguous Intent)433434- "I calculated X=5,000 but expected X=5,050. Should the formula include [specific row/column]?"435- "The discount rate cell is empty. What value should I use?"436- "This creates a circular reference. Should I restructure the calculation?"437438### Iteration Rule439440**Never deliver a spreadsheet with failed verification.** Fix → re-verify → repeat. Maximum 3 fix-and-verify cycles. After 3 failures on the same check, document and ask user.441442---443444## Phase 5: DELIVER445446### Delivery Checklist447448- [ ] File saved to output directory449- [ ] **verification_report.json saved to SAME directory as xlsx** (MANDATORY)450- [ ] Python verification: 100% pass (or documented exceptions)451- [ ] Formatting complete (colors, fonts, number formats, borders)452- [ ] Data validation in place453- [ ] Conditional formatting applied (if applicable)454- [ ] Charts render correctly (if applicable)455- [ ] Cell protection set (formula cells locked, input cells unlocked)456- [ ] Print layout set for sheets >50 rows (freeze panes, print titles)457- [ ] Assumptions documented458459**If verification_report.json is missing, DO NOT deliver. Go back to Phase 4.**460461### Deliver Format462463```464[View your spreadsheet](computer:///path/to/filename.xlsx)465466Verification: [N]/[N] checks passed. All calculations independently verified.467[Brief summary of what was built]468```469470### Iteration471472If user wants changes: understand feedback → update plan → modify → re-verify → deliver.473Never skip verification after changes, even for "small" edits.474475---476477## Google Sheets Compatibility478479If user requests Google Sheets, use ONLY compatible functions:480481```482Excel-Only Function → Google Sheets Replacement483XLOOKUP → INDEX(MATCH())484LET → Inline the expression485LAMBDA → Use helper cells486IFS → Nested IF()487SWITCH → Nested IF() or VLOOKUP on helper table488XMATCH → MATCH()489VSTACK / HSTACK → Manual ranges490Dynamic arrays → ARRAYFORMULA() wrapper491Structured refs → Use A1 notation492SUBTOTAL mode 101+ → SUMPRODUCT workaround (ignore hidden rows)493```494495Phase 4: If GSheets mode, scan ALL formulas for Excel-only functions. Flag any found.496497---498499## Reference Files500501Read ONLY the files relevant to the current task. Do NOT load all references upfront.502503| File | When to Read | Priority |504|------|-------------|----------|505| `references/decision_framework.md` | ALWAYS read first — controls question flow and safe defaults | Phase 1 |506| `references/business_domains.md` | Financial models, FP&A, Accounting, Dashboards, Sales, HR, Supply Chain, Real Estate | Phase 2 (if business) |507| `references/engineering_domains.md` | Mechanical, Electrical, Civil, Chemical, Manufacturing, Quality, Systems Engineering | Phase 2 (if engineering) |508| `references/build_patterns.md` | Code patterns, charts, data validation, pivot tables, sensitivity analysis, Google Sheets compat | Phase 3 |509| `references/formula_reference.md` | Complex formulas, function syntax, Excel vs Google Sheets differences | Phase 3 (as needed) |510| `references/verification_guide.md` | Shadow calculation patterns, tolerance handling, numerical precision | Phase 4 |511| `scripts/verify_spreadsheet.py` | Run generic verification; also contains `npv_python()`, `irr_python()`, `pmt_python()` helpers | Phase 4 |512513---514515## Code Style516517Write minimal, concise Python. No unnecessary comments or print statements. Break complex operations into small testable functions. Use type hints for verification functions.