How this skill is triggered — by the user, by Claude, or both
Slash command
/modelling-team:modelling-teamThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
---
Three specialist agents collaborate to produce a polished, error-free Excel model. Each agent has a distinct role, runs on a specific Claude model, and hands off a clear deliverable to the next.
User request
│
▼
┌─ Step 0: Clarify scope ─────────────────────────────────┐
│ Ask: Heavy (multi-tab) or Light (single-tab)? │
│ Run the Expanded Scoping Checklist (see Step 0 below). │
└──────────────────────────────────────────────────────────┘
│
▼
┌─ Step 0.5: RESEARCH (optional, Sonnet) ────────────────┐
│ For models where economic assumptions materially drive │
│ the output, run a research sprint on the top 5–15 │
│ drivers BEFORE the Architect bakes them in. Output: │
│ assumption register with sources, URLs, confidence. │
└──────────────────────────────────────────────────────────┘
│
▼
┌─ Step 1: MODEL ARCHITECT (Opus) ────────────────────────┐
│ Designs the full model blueprint: tabs, row layout, │
│ assumption cells, formula logic, scenario selector, │
│ dashboard design, formatting plan. │
│ Output: structured blueprint in markdown. │
└──────────────────────────────────────────────────────────┘
│
▼
┌─ Step 2: MODEL CODER (Sonnet) ─────────────────────────┐
│ Translates the blueprint into a Python/openpyxl script. │
│ Runs it. Produces the .xlsx file. │
└──────────────────────────────────────────────────────────┘
│
▼
┌─ Step 3: MODEL CHALLENGER (Opus) ──────────────────────┐
│ Reviews the .xlsx: checks formula logic, stress-tests │
│ assumptions, validates cross-references and scenario │
│ selector wiring, checks dashboard links. │
│ Output: PASS / PASS WITH NOTES / FAIL + fix list. │
└──────────────────────────────────────────────────────────┘
│
▼
If FAIL → fixes back to Coder → re-run Challenger
If PASS → deliver to user
Before spawning agents, ask the user:
Is this a heavy model (multiple tabs, scenarios, dashboard) or a light model (single tab)?
Don't stop at "heavy or light". Walk through this checklist with the user — surfacing these questions upfront saves 1–2 iteration cycles later.
Required:
Recommended:
When to run: Whenever the model's economic assumptions are externally researchable (CAPEX line items, salaries, lease rates, raw material costs, market sizes, conversion benchmarks). Skip for synthetic / educational / illustrative models.
How: Spawn a general-purpose Agent with web access. Task it with:
research-sourced vs needs verification — RFQ required).The Architect then receives this register and bakes the verified numbers into the blueprint, with verification flags propagating into the Assumptions tab.
Spawn a general-purpose Agent. This agent does NOT write code — it designs the model structure.
Read references/architect-prompt.md for the full agent prompt template. Fill in: {USER_REQUEST}, {MODEL_MODE}, {OUTPUT_PATH}, {ADDITIONAL_CONTEXT}, {RESEARCH_REGISTER} (optional, from Step 0.5).
These defaults exist because they were learned the hard way through iteration. The Architect prompt enforces them — but you should know they exist so you can confirm them in scoping:
channel mix × CPC per channel × conversion % → new customers, not a single "marketing spend = $X" assumption. Each channel (Google, Meta, LinkedIn, organic, etc.) gets its own CPC and conversion %, scenario-flexed.Alignment(indent=N) — never empty spacer columns. Every column has real content.CX-01, REV-02) — labels and row numbers are sufficient.MATCH(TRUE,…), FILTER, SORT, UNIQUE, XLOOKUP with spilling) — they break with #NAME in pre-2021 Excel. Use nested-IF for Payback, INDEX/MATCH for lookups.# FTE × $/FTE × Total columns — never bundle headcount and comp.input / formula / cross-sheet / header), formula logic in pseudo-Excel, number format, styleThe Architect MUST include in any heavy-mode blueprint:
Dashboard tab (always the first tab):
Dashboard!B3): a cell containing 1, 2, or 3 (mapped to Low / Base / High), with Data Validation dropdownScenario selector mechanism:
INDEX() to select the right column based on the scenario selector: =INDEX(Assumptions!B5:D5, 1, Dashboard!$B$3)=CHOOSE(B3,"Low","Base","High")='Assumptions'!C10)Spawn a general-purpose Agent. This agent writes and runs the Python script.
Read references/coder-prompt.md for the full agent prompt template. Fill in: {BLUEPRINT}, {OUTPUT_PATH}, {SCRIPT_PATH}.
The Coder MUST use this exact visual design system — derived from an established house style. Include these definitions at the top of every script:
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from openpyxl.worksheet.datavalidation import DataValidation
# ── Palette ─────────────────────────────────────────
NAVY = "1F3864"; MED_BLUE = "2F5496"; LT_BLUE = "D9E1F2"
WHITE = "FFFFFF"; LT_GREY = "F2F2F2"; MED_GREY = "D9D9D9"
INPUT_BG = "EBF3FB"; LT_GRN = "E2EFDA"; LT_OG = "FCE4D6"
LT_YLW = "FFF2CC"; AMBER = "F4B942"; HDR_GRN = "375623"
# ── Fonts ───────────────────────────────────────────
F_INPUT = Font(name="Arial", size=10, color="0000FF") # blue: editable inputs
F_CALC = Font(name="Arial", size=10, color="000000") # black: formulas
F_XSHT = Font(name="Arial", size=10, color="008000") # green: cross-sheet links
F_HDR = Font(name="Arial", size=10, bold=True, color="FFFFFF") # white bold: headers
F_LBL = Font(name="Arial", size=10) # regular labels
F_BOLD = Font(name="Arial", size=10, bold=True) # bold labels / totals
F_TITLE = Font(name="Arial", size=14, bold=True, color="FFFFFF") # title rows
F_NOTE = Font(name="Arial", size=9, italic=True, color="595959") # notes
# ── Borders & fills ─────────────────────────────────
ts = Side(style="thin", color="B8B8B8")
def tb(): return Border(left=ts, right=ts, top=ts, bottom=ts)
def fill(c): return PatternFill("solid", start_color=c)
# ── Reusable helpers ────────────────────────────────
def title_row(ws, row, text, end_col="G", h=28):
ws.merge_cells(f"A{row}:{end_col}{row}")
c = ws[f"A{row}"]
c.value = text; c.font = F_TITLE; c.fill = fill(NAVY)
c.alignment = Alignment(horizontal="center", vertical="center")
ws.row_dimensions[row].height = h
def sec(ws, row, text, end_col="G", bg=MED_BLUE):
ws.merge_cells(f"A{row}:{end_col}{row}")
c = ws[f"A{row}"]
c.value = text; c.font = F_HDR; c.fill = fill(bg)
c.alignment = Alignment(horizontal="left", vertical="center")
c.border = tb(); ws.row_dimensions[row].height = 20
def hc(ws, r, col, text, bg=MED_BLUE):
c = ws.cell(r, col, text)
c.font = F_HDR; c.fill = fill(bg)
c.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
c.border = tb(); return c
def lbl(ws, r, col, text, bold=False, bg=None):
c = ws.cell(r, col, text)
c.font = F_BOLD if bold else F_LBL
c.fill = fill(bg or (LT_GREY if bold else WHITE))
c.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
c.border = tb(); return c
def inp(ws, r, col, val, fmt="$#,##0;($#,##0);-"):
c = ws.cell(r, col, val)
c.font = F_INPUT; c.fill = fill(INPUT_BG)
c.number_format = fmt
c.alignment = Alignment(horizontal="center", vertical="center")
c.border = tb(); return c
def calc(ws, r, col, formula, fmt="$#,##0;($#,##0);-", xsh=False):
c = ws.cell(r, col, formula)
c.font = F_XSHT if xsh else F_CALC; c.fill = fill(WHITE)
c.number_format = fmt
c.alignment = Alignment(horizontal="center", vertical="center")
c.border = tb(); return c
def bold_calc(ws, r, col, formula, fmt="$#,##0;($#,##0);-", bg=LT_GRN):
c = ws.cell(r, col, formula)
c.font = F_BOLD; c.fill = fill(bg)
c.number_format = fmt
c.alignment = Alignment(horizontal="center", vertical="center")
c.border = tb(); return c
def note_row(ws, row, text, end_col="G", h=50):
ws.merge_cells(f"A{row}:{end_col}{row}")
c = ws[f"A{row}"]
c.value = text; c.font = Font(name="Arial", size=10, italic=True, color="1F3864")
c.alignment = Alignment(wrap_text=True, horizontal="left", vertical="top")
ws.row_dimensions[row].height = h
On the Dashboard tab, the Coder must create:
# Scenario selector (cell B3 = 1/2/3 → Low/Base/High)
lbl(ws_dash, 2, 1, "Select scenario:", bold=True)
inp(ws_dash, 2, 2, 2, "0") # default = 2 (Base)
calc(ws_dash, 2, 3, '=CHOOSE(B2,"Low","Base","High")', "@")
dv = DataValidation(type="list", formula1='"1,2,3"', allow_blank=False)
dv.prompt = "1=Low, 2=Base, 3=High"
ws_dash.add_data_validation(dv)
dv.add(ws_dash["B2"])
Then every scenario-dependent formula throughout the model references it:
# Instead of hardcoding base column:
calc(ws, row, col, "=INDEX(Assumptions!B10:D10,1,Dashboard!$B$2)", fmt)
'$#,##0;($#,##0);-''$#,##0.0;($#,##0.0);-''0.0%;(0.0%);-''0.0x;(0.0x);-''0' (not '#,##0' — avoids 2,026)'0.0'The generated model must be ready for a human analyst to extend — e.g., dragging formulas to new columns/periods. Every formula must use proper mixed/absolute references:
C$10. The column changes when dragged right (C→D→E), but the row stays pinned to the header row.$D$5. Both row and column are locked — dragging the formula in any direction still points to the same assumption cell.C$15 for "Revenue is always in row 15 of this tab."Dashboard!$B$2.Example: A formula in row 20 (EBITDA), column C (Year 1), that multiplies revenue (row 15) by a margin assumption (Assumptions!D8):
=C$15 * $D$8 ← light model (assumptions on same tab)
=C$15 * INDEX(Assumptions!$B$8:$D$8,1,Dashboard!$B$2) ← heavy model
When dragged to column D (Year 2), this becomes =D$15 * ... — correct.
IF(x=0,0,...) for divisions, IFERROR(IRR(...),"N/A") for IRRws.sheet_view.zoomScale = 85 on every worksheet so the model fits comfortably on screen when first openedpy -3 <script_path>, confirm .xlsx savedSpawn a general-purpose Agent. This agent is the quality gate.
Read references/challenger-prompt.md for the full agent prompt template. Fill in: {XLSX_PATH}, {BLUEPRINT_SUMMARY}, {USER_REQUEST}.
INDEX(..., Dashboard!$B$2) (or equivalent)?| Agent | Claude Model | Why |
|---|---|---|
| Architect | Opus (claude-opus-4-6) | Conceptual design requires deep reasoning about model structure, edge cases, and formula interdependencies |
| Coder | Sonnet (claude-sonnet-4-6) | Fast, precise code generation following a detailed blueprint |
| Challenger | Opus (claude-opus-4-6) | Adversarial review requires the same depth of reasoning as architecture — finding subtle formula bugs and logic gaps |
When spawning agents, you do NOT need to specify the model explicitly — the orchestrator (you) runs on whatever model the user has set. But in the agent prompt, tell each agent what it is and reference the appropriate prompt template.
If the Challenger returns FAIL:
Once the Challenger passes:
.xlsx file was savednpx claudepluginhub sartrus/modelling-team-skill --plugin modelling-team-skillUse this skill for hands-on Excel financial model work: building models from scratch (SaaS, three-statement, revenue/COGS/EBITDA), auditing a spreadsheet for formula errors, explaining or mapping out model logic, converting cell references to named ranges, running what-if or scenario analysis, goal-seeking (what input value produces a target output?), or running Monte Carlo simulations. Also use when someone has inherited an unfamiliar model, calls it a "black box", or needs a specific formula (like WACC) checked. The trigger is a user who has a spreadsheet and needs to do something with it. Do NOT use for accounting theory, tax questions, investment advice, or finance questions with no model attached.
Completes LBO Excel model templates for private equity deals by filling formulas, validating calculations, and applying adaptive professional formatting.
Reviews startup financial models for investor readiness—validates unit economics, stress-tests runway scenarios, benchmarks metrics against stage targets. Supports Excel, CSV, Google Sheets exports.