Ten self-contained spreadsheet tasks that probe how well a model creates Excel workbooks β from simple data tables with SUM formulas to multi-sheet financial models with cross-sheet references, strict color coding, DIV/0 guarding, and wide-column mappings past Z. Each task has a copy-paste prompt, a verify command, and clear pass criteria.
These tasks test the xlsx skill β the model's ability to produce well-structured, correct, and professional .xlsx files using ExcelJS. Tasks progress from create-only to edit-and-recalculate, edge-case prevention, and complex multi-workbook orchestration. Start at Task 1 and work up.
These tasks use the generate_xlsx MCP tool and ExcelJS. Knowing the conventions helps you evaluate whether the model followed the skill correctly.
| Concept | Rule | Why |
|---|---|---|
| Formulas | Always use Excel formulas, never hardcoded JS results | Spreadsheet stays dynamic when data changes |
| Assumptions | Place rates/margins in separate cells, reference them | One change updates all projections |
| Blue text | RGB 0,0,255 β hardcoded inputs the user changes | Industry-standard financial model convention |
| Black text | RGB 0,0,0 β formulas and calculations | Immediately visible what's computed vs entered |
| Green text | RGB 0,128,0 β cross-sheet links | Traces dependencies across workbooks |
| Yellow fill | RGB 255,255,0 β key assumptions | What to tweak when scenario planning |
| Zero as dash | Format $#,##0;($#,##0);- | Cleaner than showing "$0" |
| Recalc | Always recalculate after creation or edit | Formula strings need LibreOffice to resolve cached values |
| Column Z+ | Column 27 = AA, 52 = AZ, 64 = BL, not BK | Off-by-one errors in column mapping break everything |
You need one terminal with the Aperio server running on your preferred AI provider. Paste each task prompt into the chat (web UI or CLI). All output files go to trash/ β no project files are modified.
npm run start:local # or any AI_PROVIDER, port 31337
Paste each task's prompt into the chat. Use a fresh conversation per task when possible. After the model responds, check that the .xlsx file was created, then run the Verify command. The verify commands use Node.js inline scripts with exceljs β no LibreOffice needed.
If you want to re-run tasks against a different model, clean up the output files first:
rm -f trash/test-*.xlsx
Ordered by complexity: simple tables first, then single-sheet formulas, financial-model conventions, multi-sheet workbooks, editing existing files, data cleaning, wide-column handling, and edge-case prevention. Each task lists difficulty, what to look for, the prompt to paste, and the verify command.
Probes: table structure, formula usage, basic formatting
Files:1 new file (trash/test-expenses.xlsx)
trash/test-expenses.xlsx with columns: Date, Category, Description, Amount. Add 10 sample expense rows across at least 3 categories (Food, Transport, Utilities, Entertainment). Add a SUM formula at the bottom of the Amount column. Use Arial font, bold headers, and auto-fit column widths. Use an Excel formula for the total β do NOT hardcode the sum.trash/test-expenses.xlsx?node -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-expenses.xlsx').then(w=>{const s=w.getWorksheet(1),v=s.getCell(s.rowCount,2).value;if(v&&v.formula&&v.formula.includes('SUM')){console.log('OK β SUM formula found:',v.formula);process.exit(0)}else{console.log('FAIL β no SUM formula or not a formula cell:',JSON.stringify(v));process.exit(1)}})})"Probes: IF formula chaining, SUM/AVERAGE, cell referencing
Files:1 new file (trash/test-grades.xlsx)
trash/test-grades.xlsx. Columns: Student Name, Assignment 1, Assignment 2, Assignment 3, Assignment 4 (each scored 0β100), Total, Average, Letter Grade. Add 8 students with varied scores. Use Excel formulas: SUM for Total, AVERAGE for Average, and a nested IF formula for Letter Grade: A β₯ 90, B β₯ 80, C β₯ 70, D β₯ 60, F below 60. Bold the headers. Use a professional font (Arial). Make sure the letter grade formula handles all five ranges correctly.node -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-grades.xlsx').then(w=>{const s=w.getWorksheet(1);let grades={};for(let r=2;r<=9;r++){let g=s.getCell(r,8).value;if(g&&g.result)grades[s.getCell(r,1).value]=g.result};let distinct=new Set(Object.values(grades));console.log('Grades found:',Object.values(grades));if(distinct.size>=3){console.log('OK β',distinct.size,'distinct grades');process.exit(0)}else{console.log('FAIL β only',distinct.size,'distinct grades');process.exit(1)}})})"Probes: assumption extraction, cell referencing, industry-standard color/number formatting
Files:1 new file (trash/test-finmodel.xlsx)
trash/test-finmodel.xlsx. Layout:
β’ Top section β Assumptions block: Revenue Growth 10%, COGS % 60%, SG&A % 15%, Tax Rate 21%. Each in its own cell with a label.
β’ Below β Income Statement: rows for Revenue, COGS, Gross Profit, SG&A, EBIT, Taxes, Net Income. Columns: Year 1 through Year 5.
Apply strict financial-model formatting:
- Blue text (RGB 0,0,255) for all hardcoded inputs (the assumption values)
- Black text (RGB 0,0,0) for all formulas
- Yellow background (RGB 255,255,0) for the assumption value cells
- Currency: $#,##0;($#,##0);- (negatives in parens, zeros as dash)
- Percentages: 0.0%
All formulas must reference the assumption cells β no hardcoded numbers in formula expressions (e.g., use =B5*(1+$B$2), not =B5*1.1).node -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-finmodel.xlsx').then(w=>{const s=w.getWorksheet(1);let fcount=0,blue=0;for(let r=1;r<=s.rowCount;r++){for(let c=1;c<=s.columnCount;c++){const cell=s.getCell(r,c);if(cell.value&&cell.value.formula)fcount++;if(cell.font&&cell.font.color&&cell.font.color.argb==='FF0000FF')blue++}};console.log('Formulas:',fcount,'Blue cells:',blue);if(fcount>=20&&blue>=2){console.log('OK β',fcount,'formulas,',blue,'blue input cells');process.exit(0)}else{console.log('FAIL β too few formulas or blue cells');process.exit(1)}})})"Probes: multi-sheet workbook structure, cross-sheet references, green text convention
Files:1 new file (trash/test-budget.xlsx) β 3 sheets
trash/test-budget.xlsx with 3 sheets:
Sheet 1 β "Income": columns for Month (JanβDec), Salary, Freelance, Investments, and Total. Add realistic monthly values. Total row at bottom with SUM formulas.
Sheet 2 β "Expenses": columns for Month (JanβDec), Housing, Food, Transport, Utilities, Entertainment, Savings, and Total. Add realistic values. Total row with SUM. Grand Total row below.
Sheet 3 β "Summary": cells showing:=SUM(Income!E2:E13) or similar cross-sheet SUMnode -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-budget.xlsx').then(w=>{let names=w.worksheets.map(s=>s.name);let s3=w.getWorksheet('Summary');let c1=s3.getCell('B1'),c2=s3.getCell('B2'),c3=s3.getCell('B3');let crossSheet=(c1.value&&c1.value.formula&&c1.value.formula.includes('Income'))&&(c2.value&&c2.value.formula&&c2.value.formula.includes('Expenses'));let green=c1.font&&c1.font.color&&c1.font.color.argb==='FF008000';console.log('Sheets:',names);console.log('Cross-sheet formulas:',!!c1.value?.formula,!!c2.value?.formula);console.log('Green text:',green);if(crossSheet&&green){console.log('OK β cross-sheet refs with green text');process.exit(0)}else{console.log('FAIL β missing cross-sheet refs or green text');process.exit(1)}})})"Probes: parsing, missing-value handling, notes annotation, totals logic
Files:1 new file (trash/test-cleaned.xlsx)
trash/test-cleaned.xlsx. Handle the missing values:
Name|Age|City|Salary|Dept
Alice|30|NYC|55000|Eng
Bob||LA|62000|Eng
Charlie|35||47000|Mktg
Diana|28|SF||Sales
Eve|42|CHI|71000|Mktg
Frank|39|SEA|53000|Eng
Grace|31||68000|Sales
Hank|27|BOS||Mktg
Requirements:
- Bold header row with a colored background (any muted color)
- Add a "Notes" column that flags any cell with missing data (e.g., "Missing: Age, City")
- Add a Total row at the bottom: COUNTA for Name, AVERAGE for Age and Salary (ignoring blanks), department counts below
- Below the main table, add a small summary table showing count of employees per department (using formulas)
- Use Arial font throughoutnode -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-cleaned.xlsx').then(w=>{const s=w.getWorksheet(1);let notesCol=0,header=s.getRow(1);header.eachCell((c,i)=>{if(c.value&&c.value.toString().toLowerCase().includes('note'))notesCol=i});let hasNotes=notesCol>0;let hasTotal=false;for(let r=2;r<=s.rowCount;r++){let val=s.getCell(r,1).value;if(val&&val.toString().toLowerCase().includes('total'))hasTotal=true};let boldHeader=header.getCell(1).font&&header.getCell(1).font.bold;console.log('Notes column:',notesCol,'Has total row:',hasTotal,'Bold header:',boldHeader);if(hasNotes&&hasTotal&&boldHeader){console.log('OK β cleaned, annotated, totalled');process.exit(0)}else{console.log('FAIL β missing notes, totals, or bold header');process.exit(1)}})})"Probes: reading existing file, surgical edits, formatting preservation
Files:1 new file (trash/test-enhanced.xlsx) β based on the output of Task 1
Prerequisite: Complete Task 1 first, or manually create trash/test-expenses.xlsx with the structure described there.
trash/test-expenses.xlsx and save an enhanced version to trash/test-enhanced.xlsx. Changes:
1. Insert a new "VAT Amount" column between Description and Amount. VAT = 20% of the Amount. Use an Excel formula, not a hardcoded value.
2. Insert 3 new expense rows with realistic data (after row 10 but before the total).
3. Apply UK pound format Β£#,##0.00 to the Amount and VAT columns.
4. Freeze the header row.
5. The SUM formula at the bottom should still cover all data rows (including the new ones).Β£#,##0.00?node -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-enhanced.xlsx').then(w=>{const s=w.getWorksheet(1);let vatCol=0,frozen=s.views&&s.views[0]&&s.views[0].state==='frozen';s.getRow(1).eachCell((c,i)=>{if(c.value&&c.value.toString().toLowerCase().includes('vat'))vatCol=i});let vatFormula=s.getCell(3,vatCol).value;let isFormula=vatFormula&&vatFormula.formula&&vatFormula.formula.includes('*0.2');let dataRows=s.rowCount-(frozen?1:0);let poundFormat=s.getCell(3,vatCol).numFmt&&s.getCell(3,vatCol).numFmt.includes('Β£');console.log('VAT col:',vatCol,'Formula:',isFormula,'Frozen:',frozen,'Β£ format:',poundFormat,'Data rows:',dataRows);if(isFormula&&frozen&£Format&&dataRows>=13){console.log('OK β enhanced correctly');process.exit(0)}else{console.log('FAIL β missing criteria');process.exit(1)}})})"Probes: COUNTIF/SUMIF, multi-sheet orchestration, dashboard layout
Files:1 new file (trash/test-pmo.xlsx) β 3 sheets
trash/test-pmo.xlsx with 3 sheets:
Sheet 1 β "Projects": columns for Project Name, Owner, Status (Not Started / In Progress / Complete / On Hold), Priority (High/Med/Low), Start Date, End Date, Budget ($), Spent ($), Remaining (=BudgetβSpent). Add 10 projects in mixed states with varied priorities.
Sheet 2 β "Risks": columns for Risk ID, Project Name, Description, Likelihood (1β5), Impact (1β5), Risk Score (=Likelihood Γ Impact), Mitigation. Add 8 risks tied to the projects.
Sheet 3 β "Dashboard": 2βcolumn layout with formula-driven summaries:
- Count of projects per status (use COUNTIF)
- Total Budget vs Total Spent (SUMIF or SUM)
- Average Risk Score
- Count of HighβPriority projects
- Count of In Progress projects
Style the Dashboard: centered headings, alternating row background colors, clear section labels. No hardcoded summary values β all formulas.node -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-pmo.xlsx').then(w=>{let names=w.worksheets.map(s=>s.name);let proj=w.getWorksheet('Projects'),risks=w.getWorksheet('Risks'),dash=w.getWorksheet('Dashboard');let missing=[];if(!proj)missing.push('Projects');if(!risks)missing.push('Risks');if(!dash)missing.push('Dashboard');if(missing.length){console.log('FAIL β missing sheets:',missing);process.exit(1)};let projRows=0;proj.eachRow((r,i)=>{if(i>1)projRows++});riskRows=0;risks.eachRow((r,i)=>{if(i>1)riskRows++});let dashFormulas=0;dash.eachRow(r=>{r.eachCell(c=>{if(c.value&&c.value.formula)dashFormulas++})});console.log('Sheets:',names,'Projects:',projRows,'Risks:',riskRows,'Dashboard formulas:',dashFormulas);if(projRows>=10&&riskRows>=8&&dashFormulas>=4){console.log('OK β full PMO workbook');process.exit(0)}else{console.log('FAIL β missing data or formulas');process.exit(1)}})})"Probes: column-letter arithmetic, no #REF! errors, range correctness
Files:1 new file (trash/test-forecast.xlsx)
trash/test-forecast.xlsx:
- 12 months (JanβDec) as column groups. Each month has 3 sub-columns: Sales, Cost, Profit. That's 36 data columns starting at column B (B=Jan Sales, C=Jan Cost, D=Jan Profit, E=Feb Sales, β¦ going past column Z into AA, AB, ACβ¦).
- 15 product rows (Product A through Product O).
- For each product, Profit = Sales β Cost (formula).
- At the far right (after Dec Profit, around column AN), add one more column "Annual Total" showing SUM of all 12 monthly Sales for that product.
- Below row 15, add a totals row with SUM formulas for each month's Sales, Cost, and Profit.
- Bold the headers. Use currency format $#,##0 for dollar values. Set appropriate column widths.
Critical: Every formula must reference the correct column letters. Double-check that column AA is correct (not ZA), that ranges span exactly 12 months of data, and that no #REF! errors exist.node -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-forecast.xlsx').then(w=>{const s=w.getWorksheet(1);let profitFormulas=0,annualTotals=0,errors=0;for(let r=2;r<=16;r++){let p=s.getCell(r,4).value;if(p&&p.formula&&p.formula.includes('-'))profitFormulas++;let at=s.getCell(r,37).value;if(at&&at.formula&&at.formula.includes('SUM'))annualTotals++};for(let r=1;r<=s.rowCount;r++){for(let c=1;c<=s.columnCount;c++){let v=s.getCell(r,c).value;if(typeof v==='string'&&v.startsWith('#'))errors++}};console.log('Profit formulas:',profitFormulas,'Annual totals:',annualTotals,'Errors:',errors);if(profitFormulas>=14&&annualTotals>=14&&errors===0){console.log('OK β wide columns correct, zero formula errors');process.exit(0)}else{console.log('FAIL β',errors,'errors, or missing formulas');process.exit(1)}})})"Probes: #DIV/0! prevention, IFERROR, defensive formula design
Files:1 new file (trash/test-safeguards.xlsx)
trash/test-safeguards.xlsx that demonstrates correct handling of formula edge cases. The spreadsheet has two sections:
Section 1 β Division Safety: Columns for Product, Revenue, Cost, Margin %. Use an IF guard to prevent #DIV/0! when Cost is zero or blank. Add these rows: Product A (Rev 1000, Cost 600), Product B (Rev 500, Cost 0), Product C (Rev 2000, Cost 1200), Product D (Rev 0, Cost 0), Product E (Rev 800, Cost blank). The Margin % should show a dash or "0.0%" instead of #DIV/0! for rows B, D, E.
Section 2 β Growth Calculator: Columns for Year, Revenue, YoY Growth %. Use IFERROR or an IF guard to handle the first year (no prior year) and any zero-revenue edge cases. Add 6 years of data (2025β2030) with at least one zero-revenue year.
Use professional formatting: percentage format 0.0%, dashes for zero values, Arial font, bold headers.node -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-safeguards.xlsx').then(w=>{const s=w.getWorksheet(1);let errors=0,ifs=0;for(let r=1;r<=s.rowCount;r++){for(let c=1;c<=s.columnCount;c++){let v=s.getCell(r,c).value;if(typeof v==='string'&&v.startsWith('#'))errors++;if(v&&v.formula&&(v.formula.includes('IF(')||v.formula.includes('IFERROR')))ifs++}};console.log('Errors:',errors,'IF/IFERROR guards:',ifs);if(errors===0&&ifs>=2){console.log('OK β zero formula errors with IF guards');process.exit(0)}else{console.log('FAIL β',errors,'errors remaining or no IF guards');process.exit(1)}})})"Probes: end-to-end spreadsheet discipline, assumptions separation, cross-sheet integrity, recalc verification
Files:1 new file (trash/test-annual-budget.xlsx) β 4 sheets
trash/test-annual-budget.xlsx with 4 sheets, no formula errors, no hardcoded calculated values anywhere:
Sheet 1 β "Assumptions":
- Salary Growth: 5%, Bonus Rate: 15%, Rent Escalation: 3%, Inflation: 2.5%, Tax Rate: 20%, Savings Target: 20%
- Yellow background on assumption value cells, blue text
Sheet 2 β "Income":
- 12 months (JanβDec). Columns: Salary, Bonus (=Salary Γ Bonus Rate, referencing Assumptions), Investments, Other, Total Income
- Salary grows each month by (Salary Growth / 12). Reference the Assumptions sheet.
Sheet 3 β "Expenses":
- 12 months. Columns: Rent, Utilities, Food, Transport, Entertainment, Savings Contribution (=Total Income Γ Savings Target from Assumptions), Other, Total Expenses
- Rent escalates by 3% annually. Other expenses inflate at 2.5%. All rates from Assumptions.
Sheet 4 β "Summary":
- Total Annual Income (SUM cross-sheet), Total Annual Expenses, Net Savings, Savings Rate (Net Savings / Total Income)
- Cross-sheet references in green text.
- Below: a small "What If" section β add cells where the user can override the Savings Target and Tax Rate, with formulas that recalculate the entire budget based on the overridden values. Label these clearly.node -e "import('exceljs').then(E=>{new E.Workbook().xlsx.readFile('trash/test-annual-budget.xlsx').then(w=>{let names=w.worksheets.map(s=>s.name);let missing=[];['Assumptions','Income','Expenses','Summary'].forEach(n=>{if(!names.includes(n))missing.push(n)});if(missing.length){console.log('FAIL β missing:',missing);process.exit(1)};let redir=w.getWorksheet('Summary'),greenCount=0,formulaCount=0,blueCount=0,yellowCount=0;redir.eachRow(r=>{r.eachCell(c=>{if(c.value&&c.value.formula)formulaCount++;if(c.font&&c.font.color&&c.font.color.argb==='FF008000')greenCount++})});let as=w.getWorksheet('Assumptions');as.eachRow(r=>{r.eachCell(c=>{if(c.font&&c.font.color&&c.font.color.argb==='FF0000FF')blueCount++;if(c.fill&&c.fill.fgColor&&c.fill.fgColor.argb==='FFFFFF00')yellowCount++})});let errors=0;w.worksheets.forEach(s=>{s.eachRow(r=>{r.eachCell(c=>{if(typeof c.value==='string'&&c.value.startsWith('#'))errors++})})});console.log('Formulas:',formulaCount,'Green:',greenCount,'Blue:',blueCount,'Yellow:',yellowCount,'Errors:',errors);if(formulaCount>=5&&greenCount>=2&&blueCount>=2&&yellowCount>=2&&errors===0){console.log('OK β comprehensive budget with zero errors');process.exit(0)}else{console.log('FAIL β missing criteria or errors found');process.exit(1)}})})"Apply these five signals to each task after running the verify command. They measure not just "does the file exist" but also "did the model follow the skill's conventions and actually verify its own work".
| Signal | 0 = fail | 1 = pass |
|---|---|---|
| File is created at correct path | wrong path / wrong extension / wrong format | correct trash/test-*.xlsx |
| Verify command passes | red / errors / missing file | green, zero errors |
| Uses formulas, not hardcoded values | JSβcomputed values baked into cells | Excel formulas throughout |
| Follows skill conventions (colors, fonts, refs) | wrong colors, mixed conventions, no assumptions separation | matches xlsx skill standards |
| Model actually verified (ran recalc / checked) | claimed "looks good" without checking | ran a verify step or fixed errors found |
Results fill in automatically as you click the buttons above.
| Test | Result | Notes |
|---|---|---|
| 1. Expense Tracker (SUM) | β | |
| 2. Gradebook (IF Logic) | β | |
| 3. Financial Model (Color Coding) | β | |
| 4. Multi-Sheet Budget (Cross-Sheet) | β | |
| 5. Data Cleaning (Missing Values) | β | |
| 6. Insert & Enhance (Edit Existing) | β | |
| 7. PMO Workbook (COUNTIF/SUMIF) | β | |
| 8. Wide Columns Past Z | β | |
| 9. Formula Edge Case Guards | β | |
| 10. Full Annual Budget | β | |
| TOTAL: ____ / 10 passed | ||
.xlsx files with correct formulas and formatting.The task order traces a curve from shallow to deep spreadsheet understanding:
basic tables (1) β formulas with IF logic (2) β formatting conventions (3) β cross-sheet orchestration (4) β data cleaning (5) β editing existing files (6) β multi-sheet dashboards with COUNTIF/SUMIF (7) β wide-column mapping past Z (8) β defensive formula design (9) β comprehensive budget (10)
A model that drops off after Task 2 knows table creation but not formula logic. One that drops off after Task 6 understands creating and editing but not complex orchestration. A model that passes all 10 understands spreadsheet creation β and its pitfalls β deeply.