Dcf Model
Wind-Alice/AliceMarket
Real DCF (Discounted Cash Flow) model creation for equity valuation.
A skill your agent uses when the user wants to build a financial model - 3-statement model, DCF valuation, LBO, SaaS unit economics, sensitivity / scenario analysis, debt schedule, or fundraising…
$ npx skills add FerroxLabs/wayland --skill officecli-financial-model -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install FerroxLabs/wayland officecli-financial-model --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/FerroxLabs/wayland.git skills-src && mkdir -p .claude/skills && cp -r skills-src/src/process/resources/skills/officecli-financial-model .claude/skills/officecli-financial-model && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "officecli-financial-model" agent skill from https://github.com/FerroxLabs/wayland/tree/main/src/process/resources/skills/officecli-financial-model into .claude/skills/officecli-financial-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "officecli-financial-model", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/FerroxLabs/wayland/tree/main/src/process/resources/skills/officecli-financial-modelType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add FerroxLabs/wayland --skill officecli-financial-model -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install FerroxLabs/wayland officecli-financial-model --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/FerroxLabs/wayland.git skills-src && mkdir -p .agents/skills && cp -r skills-src/src/process/resources/skills/officecli-financial-model .agents/skills/officecli-financial-model && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "officecli-financial-model" agent skill from https://github.com/FerroxLabs/wayland/tree/main/src/process/resources/skills/officecli-financial-model into .agents/skills/officecli-financial-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "officecli-financial-model", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add FerroxLabs/wayland --skill officecli-financial-model -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install FerroxLabs/wayland officecli-financial-model --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/FerroxLabs/wayland.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/src/process/resources/skills/officecli-financial-model .cursor/skills/officecli-financial-model && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "officecli-financial-model" agent skill from https://github.com/FerroxLabs/wayland/tree/main/src/process/resources/skills/officecli-financial-model into .cursor/skills/officecli-financial-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "officecli-financial-model", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/FerroxLabs/wayland.git --path src/process/resources/skills/officecli-financial-model--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add FerroxLabs/wayland --skill officecli-financial-model -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install FerroxLabs/wayland officecli-financial-model --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/FerroxLabs/wayland.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/src/process/resources/skills/officecli-financial-model .gemini/skills/officecli-financial-model && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "officecli-financial-model" agent skill from https://github.com/FerroxLabs/wayland/tree/main/src/process/resources/skills/officecli-financial-model into .gemini/skills/officecli-financial-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "officecli-financial-model", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install FerroxLabs/wayland officecli-financial-modelInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add FerroxLabs/wayland --skill officecli-financial-model -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/FerroxLabs/wayland.git skills-src && mkdir -p .github/skills && cp -r skills-src/src/process/resources/skills/officecli-financial-model .github/skills/officecli-financial-model && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "officecli-financial-model" agent skill from https://github.com/FerroxLabs/wayland/tree/main/src/process/resources/skills/officecli-financial-model into .github/skills/officecli-financial-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "officecli-financial-model", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add FerroxLabs/wayland --skill officecli-financial-model -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install FerroxLabs/wayland officecli-financial-model --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/FerroxLabs/wayland.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/src/process/resources/skills/officecli-financial-model .opencode/skills/officecli-financial-model && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "officecli-financial-model" agent skill from https://github.com/FerroxLabs/wayland/tree/main/src/process/resources/skills/officecli-financial-model into .opencode/skills/officecli-financial-model/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "officecli-financial-model", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
officecli-financial-modelA skill your agent uses when the user wants to build a financial model - 3-statement model, DCF valuation, LBO, SaaS unit economics, sensitivity / scenario analysis, debt schedule, or fundraising…
Officecli Financial Model is an agent skill from FerroxLabs/wayland. Use this skill when the user wants to build a financial model - 3-statement model, DCF valuation, LBO, SaaS unit economics, sensitivity / scenario analysis, debt schedule, or fundraising projections - in Excel. Trigger on: 'financial model', '3-statement model', 'P&L + BS + CF', 'DCF', 'WACC', 'NPV', 'terminal value', 'LBO', 'debt schedule', 'cash sweep', 'MOIC', 'IRR / XIRR', 'sensitivity table', 'scenario analysis', 'ARR model', 'unit economics', 'CAC / LTV', 'cap table forecast'. Output is a single…
Its SKILL.md is about 12k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Business, Finance & HR, covering Financial modeling and Excel spreadsheets. It works with Microsoft Excel. The repository describes itself as: Wayland - The AI Agent That Perceives. Reasons. Acts. Evolves. The licence is AGPL-3.0.
8 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 4c030c7. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
jqpython3From the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
schemas.openxmlformats.orgFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Officecli Financial Model loads about 12k tokens when it runs. Until then it costs about 215 tokens; SKILL.md has 4,138 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from FerroxLabs/wayland at commit 4c030c7, republished under its AGPL-3.0 licence (© FerroxLabs). 4,138 words, ~12,080 tokens.
.claude/skills/officecli-financial-model/SKILL.md (or your agent's skills folder).This skill is a scene layer on top of officecli-xlsx. Every xlsx hard rule - shell quoting, incremental execution, Help-First Rule, visual delivery floor, CFO 4-color code (blue input / black formula / green cross-sheet / yellow-fill assumption), number-format standards (years as text, zero as -, % one decimal, negatives in parens), assumption-cell discipline, CSV batch import, chart data-feed forms (a/b/c), the 5-gate Delivery cycle, cache-drift guidance, Known Issues (the cross-sheet ! trap, batch + resident for formulas, renderer caveats) - is inherited, not re-taught. This file adds only what a financial model requires on top: three-zone architecture, 3 model-type recipes (3-statement / DCF / LBO), sensitivity + scenario protocols, financial-function patterns, circular-reference discipline, and model-specific Delivery Gates 4–6.
When the xlsx base rules cover it, the text here says → see xlsx v2 §X. Read skills/officecli-xlsx/SKILL.md first if you have not.
Wayland Desktop supplies a checksum-pinned native officecli runtime. Do not install, update, download, or replace it from this skill. Native authoring requires the exact verified 1.0.136 version. If officecli --version or the required officecli --help surface fails, stop and use Wayland's reinstall/update recovery path; never bootstrap mutable code or silently switch to hosted-credit generation.
This skill teaches what a financial model requires, not every CLI flag. When a prop name / alias / enum is uncertain, consult help BEFORE guessing: officecli help xlsx [element] [--json]. Help is pinned to installed version - when this skill and help disagree, help wins. Every --prop X= below was verified against officecli help xlsx <element> on v1.0.63.
Inherits xlsx v2. Read skills/officecli-xlsx/SKILL.md first. This skill assumes you know create / open / close, set values/formulas, batch heredocs for cross-sheet formulas, /SheetName/A1 paths, named ranges, the 5-gate Delivery cycle, the cross-sheet ! trap, and that cross-sheet formulas go non-resident (single batch OR individual set), never batch-while-resident.
Shell quoting, incremental execution, $FILE convention → see xlsx v2 §Shell & Execution Discipline. Same rules: quote every [N] path, single-quote any prop containing $ (every number format here - $#,##0;($#,##0);"-" - needs single quotes), no hand-written \$/\t/\n, one command at a time. Examples below use $FILE (FILE="model.xlsx").
A financial model is an xlsx with a decision-grade, formula-driven layer: every output traces an unbroken chain to blue-font assumptions, every statement balances every period, every valuation is re-auditable. Eight deltas on top of a general xlsx:
=B5*(1+Assumptions!GrowthRate), never =B5*1.05.Assets − Liab − Equity = 0, CF.EndingCash = BS.Cash. Gate 4 fails on IMBALANCED.INDEX/MATCH switch, or Base/Upside/Downside cols. Excel Data Tables not reliably supported - manual grids only.0 ships a wrong number to non-recalculating readers. Gate 5 spot-checks.calc.iterate=true. Accidental circularity is broken algebra - never papered with iterate.WACC, TaxRate, TerminalGrowth, ExitMultiple, ChurnRate. Declared-unused names are dead decoration - Gate 6 flags.Stay in xlsx base for: budget trackers, CSV-to-report dumps, operational KPI sheets, simple templates, cap tables without forecast logic. Use this skill only when the ask mentions: 3-statement / DCF / WACC / NPV / TV / LBO / debt schedule / MOIC / IRR / unit economics / ARR roll-forward / sensitivity grid / scenario switch / pro forma.
Every model in this skill builds on three zones. Name them, tab-color them, and enforce them with executable audits. Breaking the zone rule is the single most common cause of an unauditable model.
| Zone | Sheet names (convention) | Tab color | Content | Hardcodes | Formulas |
|---|---|---|---|---|---|
| Inputs | Assumptions, Inputs, Drivers | Yellow FFC000 | Raw drivers: growth rates, margins, tax, WACC, FTE, pricing, working-capital days | Blue 0000FF on every cell | Allowed only for derived assumptions (e.g. =MonthlyARPU*12) |
| Calc | P&L, Balance Sheet, Cash Flow, DCF, Debt, ARR | Blue 4472C4 | All derivations and statements | Zero (enforced by Gate 6) | Black 000000 for same-sheet, green 008000 for cross-sheet |
| Outputs | Summary, Dashboard, Sensitivity, Returns | Green 70AD47 | KPIs, sensitivity grids, charts, returns waterfall | Only for labels (non-numeric); Gate 6 counts numeric hardcodes → 0 | Black / green per above |
Build order is cross-zone-aware. Assumptions first, then Calc bottom-up on the dependency chain (IS → BS → CF for 3-statement; FCF → WACC → NPV for DCF), then Outputs last. Building Outputs first caches 0 everywhere and downstream inherits zeros.
Executable zone audit (run before Gate 4):
# Calc zone: zero numeric hardcodes allowed. NOTE: `:not(:has(formula))` pseudo doesn't filter on v1.0.63+ - filter via jq on .format.formula == null.
HARDCODE=$(officecli query "$FILE" 'cell[type=Number]' --json | jq '[.data.results[] | select(.format.formula == null) | select(.path | test("/(P&L|Balance Sheet|Cash Flow|DCF|Debt|ARR)/"))] | length')
[ "$HARDCODE" -eq 0 ] && echo "Zone audit OK" || { echo "REJECT: $HARDCODE hardcoded numeric cells on Calc sheets - move to Assumptions"; exit 1; }
# Assumptions zone: should be non-zero.
INPUTS=$(officecli query "$FILE" '/Assumptions/cell[type=Number]' --json | jq '[.data.results[] | select(.format.formula == null)] | length')
[ "$INPUTS" -ge 5 ] && echo "Assumptions has $INPUTS hardcoded drivers" || echo "WARN: Assumptions has only $INPUTS inputs"When the ask contains "print" / "one-pager" / "board" / "investor" / "IC memo" / "LP update", the print pipeline must emit only the Outputs zone. Two artefacts:
# 1. Print_Area scoped to the Outputs sheet (Summary or Dashboard).
officecli add "$FILE" / --type namedrange --prop name=_xlnm.Print_Area --prop scope=Summary --prop 'refersTo=Summary!$A$1:$H$40'
# 2. Hide every non-Outputs sheet - Print_Area scope alone does NOT stop the print pipeline from emitting every visible sheet.
for S in Assumptions 'P&L' 'Balance Sheet' 'Cash Flow' DCF WACC Debt FCF 'S&U' Exit Returns; do
officecli raw-set "$FILE" /workbook --xpath "//x:sheet[@name='$S']" --action setattr --xml "state=hidden" || true
done
# 3. fit-to-page landscape on Outputs sheet.
officecli raw-set "$FILE" /Summary --xpath "//x:worksheet" --action prepend --xml '<sheetPr xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><pageSetUpPr fitToPage="1"/></sheetPr>'Delete any Print_Area set on Calc sheets - conflicting scopes emit multi-page output with Assumptions / statement sheets leaking.
Three facts cause silent wrong numbers: (1) new formulas ship without cached values - Excel recomputes on open, HTML preview / older viewers do not; (2) downstream written in the same sequence as upstream caches 0 from upstream's pre-cache state; (3) cross-sheet batch while resident is open deadlocks at 3–5 ops.
Discipline (every recipe):
P&L → BS → CF (3-statement); FCF → WACC → NPV → Sensitivity (DCF); S&U → Debt → P&L → CF → Returns (LBO).set on every summary / valuation / balance-check cell, non-resident.officecli get "$FILE" /Summary/B2 --json | jq .format.cachedValue returns non-zero non-null. null ≠ 0: null means Excel will compute on open (OK for delivery); 0 is a cached lie. If 0 persists: close residents, re-set; still 0 → cache-fallback (§Financial function patterns).Each recipe below is runnable skeleton, not finance theory. Substitute numbers; don't restructure. All recipes assume FILE="model.xlsx" is set and you have run officecli create "$FILE" + officecli open "$FILE". Close with officecli close "$FILE" at the end.
What this recipe produces. 4 sheets: Assumptions, P&L, Balance Sheet, Cash Flow, plus Summary. Year columns 2024A · 2025E · 2026E · 2027E. Balance-check row on BS; cash-reconciliation row on CF. Every statement row = formula → Assumptions.
Build order (MANDATORY). Assumptions → P&L → Balance Sheet → Cash Flow → Summary. Do NOT build BS before P&L - RetainedEarnings depends on NI. Do NOT build CF before BS - CF.OpeningCash = prior period CF.EndingCash self-chain requires BS cash anchored for Y1. The skill's Gate 4 balance check fails silently if order is wrong.
Step 1 - sheets + tab colors + freeze panes.
officecli add "$FILE" / --type sheet --prop name=Assumptions --prop tabColor=FFC000
officecli add "$FILE" / --type sheet --prop name=P&L --prop tabColor=4472C4
officecli add "$FILE" / --type sheet --prop name='Balance Sheet' --prop tabColor=4472C4
officecli add "$FILE" / --type sheet --prop name='Cash Flow' --prop tabColor=4472C4
officecli add "$FILE" / --type sheet --prop name=Summary --prop tabColor=70AD47
officecli set "$FILE" /Assumptions --prop freeze=B2
officecli set "$FILE" /P&L --prop freeze=B3
officecli set "$FILE" "/Balance Sheet" --prop freeze=B3
officecli set "$FILE" "/Cash Flow" --prop freeze=B3Step 2 - assumptions (blue, yellow-fill on key drivers). Year headers row 2, labels down col A, blue numeric inputs on B:E. Drivers: RevenueGrowth, GrossMargin, OpExRatio, TaxRate, DaysReceivable/Inventory/Payable, CapExRatio, DepreciationYears. font.color=0000FF on B:E. Yellow-fill (fill=FFFF00) the 3–5 scenario-switched drivers.
Declare named ranges for ≥3-use drivers and reference them (StartingARR, TaxRate, OpeningCash, GrowthRate, GrossMargin). Formulas: =StartingARR not =Assumptions!B4; =EBT*TaxRate not =EBT*Assumptions!B8. Declared-unused names = dead decoration, Gate 6 rejects.
Step 3 - P&L rows (all formulas). Rows: Revenue / COGS / Gross Profit / OpEx / EBITDA / D&A / EBIT / Interest / EBT / Tax / Net Income. Every row = formula referencing Assumptions or prior-row cells. Example revenue-side block - substitute your row numbers. Row-map for this example: B3=Revenue, B4=COGS, B5=Gross Profit, B7=OpEx, B9=EBITDA, B10=EBIT, B15=Net Income. Submit as single non-resident batch:
cat <<'EOF' | officecli batch "$FILE"
[
{"command":"set","path":"/P&L/B3","props":{"formula":"Assumptions!B5","font.color":"008000"}},
{"command":"set","path":"/P&L/C3","props":{"formula":"B3*(1+Assumptions!C6)"}},
{"command":"set","path":"/P&L/D3","props":{"formula":"C3*(1+Assumptions!D6)"}},
{"command":"set","path":"/P&L/E3","props":{"formula":"D3*(1+Assumptions!E6)"}},
{"command":"set","path":"/P&L/B4","props":{"formula":"-B3*(1-Assumptions!B7)"}},
{"command":"set","path":"/P&L/B5","props":{"formula":"B3+B4"}}
]
EOFAssumptions refs (B5, C6, B7) are also placeholder rows - better: define named ranges for each driver (Step 2) so formulas read =StartingRevenue*(1+RevenueGrowth_Y2) regardless of row layout. Repeat for OpEx / D&A / Interest / Tax / NI. font.color=008000 on every cross-sheet-reference cell; same-sheet cells default 000000. numFmt='$#,##0;($#,##0);"-"' on all $ rows.
Step 4 - Balance Sheet rows (all formulas). Assets = Cash + AR + Inventory + Net PP&E. Liab = AP + Debt. Equity = OpeningEquity + RetainedEarnings. Working-capital rows use Days assumptions: AR = Revenue × DaysReceivable / 365. Net PP&E rolls forward: Beg + CapEx − Depreciation. BS.Cash is NOT an independent plug - it MUST equal 'Cash Flow'!B<ending-cash-row> (populated in Step 5).
Retained Earnings - live formula every period. BS.RE(t) = BS.RE(t-1) + 'P&L'!NI(t) − Dividends(t). Hardcoded RE rounds to whole dollar → BS shows ±$1 off every period (CFO reads "model doesn't balance"). For Y1 Historical RE (no prior NI), compute via BS identity as a live formula: BS!RE_Y1 = TotalAssets − TotalLiabilities − PaidInCapital. Blue-font + classic comment on the Y1 cell; Y2..Y5 stay NI-driven.
Step 5 - Cash Flow rows (all formulas). Operating: NI + D&A − ΔWorkingCapital. Investing: −CapEx. Financing: ΔDebt − Dividends. Ending Cash = Opening + Operating + Investing + Financing. Year 2+ Opening Cash = prior period Ending Cash - self-chain on the same sheet: C17 = B19, D17 = C19, E17 = D19. The Y1 OpeningCash is an Assumptions input.
Step 6 - Balance check + cash reconciliation rows (hard delivery checks). Row-map for this example: Balance Sheet: B10=Total Assets, B15=Total Liab, B17=Total Equity, B18=Balance Check; Cash Flow: B5=BS.Cash (cross-sheet anchor), B19=CF.Ending Cash, B21=CF-BS Cash Recon. Substitute your layout's rows - the logic is the check, not the cell addresses.
cat <<'EOF' | officecli batch "$FILE"
[
{"command":"set","path":"/Balance Sheet/B18","props":{"formula":"IF(ABS(B10-B15-B17)<0.01,\"OK\",\"IMBALANCED: \"&ROUND(B10-B15-B17,0))","bold":"true","font.color":"000000"}},
{"command":"set","path":"/Cash Flow/B21","props":{"formula":"IF(ABS(B19-'Balance Sheet'!B5)<0.01,\"OK\",\"CF != BS CASH: \"&ROUND(B19-'Balance Sheet'!B5,0))","bold":"true"}}
]
EOFReplicate across columns C/D/E. Apply red fill (fill=FFC7CE) conditionally via type=containsText --prop text=IMBALANCED or text="CF !=". Gate 4 queries these rows and refuses delivery on any IMBALANCED.
Step 7 - cache refresh + format pass. Re-set every summary cell on Summary, every balance-check / recon cell, and every cross-sheet reference on BS / CF (non-resident, single batch per sheet). Apply column widths (col[A]=28, col[B:E]=15), numberformat='$#,##0;($#,##0);"-"' on all dollar rows, header fills (fill=1F3864, font.color=FFFFFF, bold=true) on section-header rows (REVENUE / COGS / ASSETS / LIABILITIES). Header fill must cover A:E, not just the label cell (→ xlsx v2 §visual floor).
Step 8 - Summary / Dashboard KPIs + charts. Minimum 4 KPIs: Revenue 27E, EBITDA Margin 27E, Ending Cash 27E, Net Income CAGR - each a formula referencing a statement cell, green font.
Minimum 3 charts on any Dashboard delivered to a board / executive audience - one chart is a draft, three is a deliverable. Pre-populate Summary!A10:E13 with Gross Margin / EBITDA Margin / NI Margin ratio rows (formulas referencing P&L) before adding the margin chart.
# (1) Top-line trend (Revenue + EBITDA).
officecli add "$FILE" /Summary --type chart --prop chartType=column --prop dataRange='P&L!A2:E5' --prop title='Revenue & EBITDA' --prop width=14cm --prop height=8cm
# (2) Margin trend (Gross / EBITDA / NI margin).
officecli add "$FILE" /Summary --type chart --prop chartType=line --prop dataRange='Summary!A10:E13' --prop title='Margin trend' --prop width=14cm --prop height=8cm
# (3) Cash trajectory (Ending Cash ± Runway).
officecli add "$FILE" /Summary --type chart --prop chartType=area --prop dataRange='Cash Flow!A19:E19' --prop title='Ending cash' --prop width=14cm --prop height=8cmVerification (run all three):
# Balance check every period must say OK
officecli get "$FILE" "/Balance Sheet/B18:E18" --json | jq '.data[].cachedValue // .data[].value'
# Cash recon every period must say OK
officecli get "$FILE" "/Cash Flow/B21:E21" --json | jq '.data[].cachedValue // .data[].value'
# Summary KPIs are plausible numbers, not 0 or null
officecli get "$FILE" "/Summary/B2:B5" --json | jq '.data[].cachedValue'What this recipe produces. Sheets: Assumptions, FCF (10-year forecast), WACC (panel), DCF (NPV + TV + equity bridge), Sensitivity (2-axis grid). Output: Implied Equity Value + Implied Per-Share, with a WACC × g sensitivity.
Build order. Assumptions → FCF → WACC → DCF → Sensitivity.
Step 1 - named ranges for key drivers. DCF's readability depends on names. Every formula below uses WACC, TaxRate, g - not $B$6:
cat <<'EOF' | officecli batch "$FILE"
[
{"command":"add","parent":"/","type":"namedrange","props":{"name":"WACC","ref":"WACC!$B$12"}},
{"command":"add","parent":"/","type":"namedrange","props":{"name":"TaxRate","ref":"Assumptions!$B$8"}},
{"command":"add","parent":"/","type":"namedrange","props":{"name":"TerminalGrowth","ref":"Assumptions!$B$15"}},
{"command":"add","parent":"/","type":"namedrange","props":{"name":"NetDebt","ref":"Assumptions!$B$20"}},
{"command":"add","parent":"/","type":"namedrange","props":{"name":"SharesOut","ref":"Assumptions!$B$21"}}
]
EOFStep 2 - FCF build (10 years). Columns B:K = Y1..Y10. Rows: Revenue (from growth) / EBIT (revenue × margin) / EBIT × (1 − TaxRate) (NOPAT) / + D&A / − CapEx / − ΔNWC / = FCF. Use Assumptions-driven ratios (CapEx = Revenue × CapExRatio). All cells formulas, black font, numFmt='$#,##0;($#,##0);"-"'.
Step 3 - WACC panel. On WACC sheet, an 8-row panel: Risk-free rate / Equity risk premium / Beta / Cost of equity (=Rf + β×ERP) / Pre-tax debt cost / After-tax debt cost (=×(1−TaxRate)) / Equity weight / Debt weight / WACC (=We×Re + Wd×Rd_after_tax). Inputs blue; derived rows black.
Step 4 - Terminal value + NPV + equity bridge. Row-map: DCF: B/C 3=TV, 4=PV explicit FCF, 5=PV terminal, 6=EV, 7=Net Debt, 8=Equity Value, 9=Per-Share; FCF: row 2 = periods (1..10), row 11 = FCF, B:K = Y1..Y10. Substitute your rows. Notes column cells use {"value":"text"}, never {"formula":"..."} - formula-style prose yields #NAME? on open (see callout after Recipe C Step 5). On DCF sheet:
cat <<'EOF' | officecli batch "$FILE"
[
{"command":"set","path":"/DCF/B3","props":{"value":"Terminal value (Gordon growth)"}},
{"command":"set","path":"/DCF/C3","props":{"formula":"FCF!K11*(1+TerminalGrowth)/(WACC-TerminalGrowth)","font.color":"008000","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/DCF/B4","props":{"value":"PV of explicit-period FCF (10 yr)"}},
{"command":"set","path":"/DCF/C4","props":{"formula":"SUMPRODUCT(FCF!B11:K11/(1+WACC)^FCF!B2:K2)","font.color":"008000","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/DCF/B5","props":{"value":"PV of terminal value"}},
{"command":"set","path":"/DCF/C5","props":{"formula":"C3/(1+WACC)^10","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/DCF/B6","props":{"value":"Enterprise value"}},
{"command":"set","path":"/DCF/C6","props":{"formula":"C4+C5","bold":"true","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/DCF/B7","props":{"value":"Less: Net debt"}},
{"command":"set","path":"/DCF/C7","props":{"formula":"-NetDebt","font.color":"008000","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/DCF/B8","props":{"value":"Equity value"}},
{"command":"set","path":"/DCF/C8","props":{"formula":"C6+C7","bold":"true","font.color":"000000","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/DCF/B9","props":{"value":"Implied per-share"}},
{"command":"set","path":"/DCF/C9","props":{"formula":"C8/SharesOut","bold":"true","numberformat":"$0.00"}}
]
EOFWhy SUMPRODUCT not NPV. NPV(rate, cross_sheet_range) silently caches 0 on v1.0.63 - ships a wrong valuation to any non-recalculating reader. SUMPRODUCT(values/(1+rate)^periods) is algebraically equivalent and caches correctly (period row FCF!B2:K2 = 1..10 is a one-time setup). For irregular dates (XNPV), use SUMPRODUCT(values/(1+rate)^((dates-base_date)/365)). See §Known Issues.
Step 5 - 2-axis sensitivity grid (WACC × g). 5×5 grid. Rows = WACC values 7.5% ... 11.5%, cols = g values 1.5% ... 3.5%. Each cell = one self-contained formula re-running the DCF with the grid's WACC and g substituted. Template:
# Cell D14 (first data cell, grid anchor at C14 = WACC label, C15 = first WACC value)
# Substitute $D$13 (this cell's g) and $C15 (this cell's WACC) into a replicated EV + equity formula.
cat <<'EOF' | officecli batch "$FILE"
[
{"command":"set","path":"/Sensitivity/D15","props":{"formula":"(NPV($C15,FCF!$B$11:$K$11)+(FCF!$K$11*(1+D$14)/($C15-D$14))/(1+$C15)^10+(-NetDebt))/SharesOut","numberformat":"$0.00"}}
]
EOFCopy the formula across D15:H19 (5×5 grid). Row 14 carries g values (blue input); column C carries WACC values (blue input). Row 13 and column B carry labels. Apply 3-color gradient CF for quick-read (green = upside, red = downside):
officecli add "$FILE" /Sensitivity --type conditionalformatting \
--prop type=colorScale --prop ref=D15:H19No Excel Data Tables. Excel's native /Data/Table 2-variable table is not reliably supported via the CLI - each grid cell MUST be an explicit formula. Copy the template, do not try Data Table input cells.
Verification.
officecli get "$FILE" "/DCF/C8" --json | jq .format.cachedValue # equity value, plausible $
officecli get "$FILE" "/DCF/C9" --json | jq .format.cachedValue # per-share, in $XX.XX range
officecli get "$FILE" "/Sensitivity/F17" --json | jq .format.cachedValue # grid center cell, plausibleIf C8 or C9 cache 0, re-set them (non-resident) - see §Build-order & cache-drift.
What this recipe produces. Sheets: Assumptions, S&U (Sources & Uses), Debt (multi-tranche schedule), P&L (5-yr), CF, Exit / Returns. Outputs: MOIC, IRR, and a 4-tier returns waterfall. LBO is the stress test - expect circular refs (interest ↔ cash), deepest cross-sheet chains, and the heaviest use of named ranges.
Build order. Assumptions → S&U → P&L → Debt → CF → Exit → Returns. P&L before Debt (debt interest depends on P&L EBIT for coverage checks); Debt before CF (CF uses interest + principal amortization). Enable calc.iterate before Step 5.
Step 1 - Sources & Uses (balance required, every fee line itemized).
Uses = Purchase_EV (EntryEBITDA × EntryMultiple) + Transaction_fees (Purchase_EV × TxnFeePct, typ 1.5–2.5%)
+ Financing_fees ((Senior + Mezz) × FinFeePct, typ 1–3%) + Refinanced_debt
Sources = Senior_TLB + Mezz + Revolver_drawn + Sponsor_equitySponsor equity - pick one, never both. (a) Stated: Sponsor_equity = Assumptions!SponsorEquity, then scale senior/mezz so Sources = Uses (fees absorbed by debt, not a silent plug). (b) Solved: Sponsor_equity = Uses − Senior − Mezz − Revolver − Refinanced, label "Sponsor Equity (solved)", no standalone Assumptions ref. Hardcoded SponsorEquity PLUS a =Uses − Senior − Mezz plug guarantees silent fee absorption - stated $140M vs plug $194.67M = $54.67M unaccounted fees, CFO rejection on sight.
# Sources = Uses hard check.
officecli set "$FILE" /S&U/B12 --prop formula='IF(ABS(SUM(B4:B7)-SUM(B9:B11))<1,"BALANCED","S&U IMBALANCE: "&ROUND(SUM(B4:B7)-SUM(B9:B11),0))' --prop bold=true
# Stated-vs-plug consistency (Gate 4 addendum; only run if you chose pattern (a)).
STATED=$(officecli get "$FILE" /Assumptions/B12 --json | jq -r '.format.cachedValue // "null"')
PLUGGED=$(officecli get "$FILE" /S&U/B10 --json | jq -r '.format.cachedValue // "null"') # B10 = sponsor-equity row on S&U
DELTA=$(python3 -c "print(abs(float('$STATED') - float('$PLUGGED')))" 2>/dev/null || echo 99999)
python3 -c "import sys; sys.exit(0 if float('$DELTA') <= 1 else 1)" && echo "S&U sponsor OK (stated=$STATED plug=$PLUGGED)" || { echo "REJECT Gate 4 S&U: stated $STATED ≠ plug $PLUGGED (Δ=$DELTA) - fees silently absorbed"; exit 1; }Every non-sponsor line on S&U is a blue Assumptions input (target EBITDA, entry multiple, fee %s) or a derived formula. No hardcoded Uses / Sources numbers.
Step 2 - Debt schedule (multi-tranche). One row per tranche per year. Columns: BeginningBalance / Mandatory amortization / Cash sweep / EndingBalance / AverageBalance / InterestExpense. Senior TLB: 1% mandatory amortization + all excess cash to sweep. Mezz: 0% amortization, interest-only cash-pay. Row-map for this example (senior TLB tranche, year 2 column C): C4=Beginning Balance, C5=Mandatory Amort, C6=Ending Balance, C7=Cash Sweep, C8=Average Balance, C9=Interest Expense. CF!C20 = free cash available to sweep (year-2 ending cash pre-sweep on CF sheet). Substitute your tranche row block per layout.
# year 2 senior TLB
cat <<'EOF' | officecli batch "$FILE"
[
{"command":"set","path":"/Debt/C4","props":{"formula":"B6"}},
{"command":"set","path":"/Debt/C5","props":{"formula":"-C4*Assumptions!$B$30","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/Debt/C6","props":{"formula":"C4+C5+C7","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/Debt/C7","props":{"formula":"-MIN(-CF!C20,C4+C5)"}},
{"command":"set","path":"/Debt/C8","props":{"formula":"(C4+C6)/2","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/Debt/C9","props":{"formula":"-C8*Assumptions!$B$31","numberformat":"$#,##0;($#,##0);\"-\""}}
]
EOF
# Add the sweep-rule comment as a classic comment (comment is NOT a cell prop - separate --type comment).
officecli add "$FILE" /Debt --type comment --prop ref=C7 --prop text='cash sweep capped at available cash and remaining tranche balance'Revolver capacity cap. If your deal uses a revolver tranche, the revolver balance each period is bounded by the commitment ceiling:
Revolver_Balance = MIN(Assumptions!RevolverCapacity, MAX(0, prior_revolver + draw − paydown))Without the MIN(capacity, ...) outer, a shortfall quarter silently over-draws the facility.
Adjust row indices to your layout. Repeat for each tranche (senior / mezz / revolver) and each year.
Step 3 - P&L (5-year) + interest from Debt. P&L interest row pulls from Debt: Interest = 'Debt'!TotalInterestRowY<N>. This creates the circular reference: Interest → NI → CF → Cash Sweep → Debt balance → Interest.
Write-order warning. calc.iterate=true governs recalculation, not write-phase. Appending the closing leg of a cross-sheet ring to a file that already contains the ring deadlocks the engine at 100% CPU regardless of iterate. For complex rings (multi-tranche LBO, revolver + TLB + mezz), use §Write-order surgery below (de-ring → write downstream → re-ring). Enable calc.iterate=true BEFORE writing ring formulas:
officecli set "$FILE" / --prop calc.iterate=true --prop calc.iterateCount=100 --prop calc.iterateDelta=0.001iterate converges via successive approximation for naturally-dampening loops (higher interest → less cash → less sweep → higher balance, bounded by EBIT). #REF! or divergent values = pause; fix algebra, do not raise iterateCount to 1000.
Step 4 - CF + cash sweep. Ending cash = Opening + CFO − CapEx − Mandatory amort − Cash sweep. Cash sweep = MIN(freeCashAfterCapEx, seniorDebtBalance + seniorMandatoryAmort). The MIN cap prevents swept-below-zero.
Step 5 - Exit + Returns. Row-map: Exit: B3=Exit EV, B4=Less: remaining debt, B5=Exit equity to sponsor; Returns: B3=MOIC, B4=IRR.
# Values/formulas - single non-resident batch.
cat <<'EOF' | officecli batch "$FILE"
[
{"command":"set","path":"/Exit/B3","props":{"formula":"'P&L'!F8*Assumptions!$B$25","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/Exit/B4","props":{"formula":"-('Debt'!F6+'Debt'!F13)","font.color":"008000","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/Exit/B5","props":{"formula":"B3+B4","bold":"true","numberformat":"$#,##0;($#,##0);\"-\""}},
{"command":"set","path":"/Returns/B3","props":{"formula":"'Exit'!B5/('S&U'!B9)","numberformat":"0.00\"x\""}},
{"command":"set","path":"/Returns/B4","props":{"formula":"IRR({-'S&U'!B9,0,0,0,0,'Exit'!B5})","numberformat":"0.0%"}}
]
EOF
# Classic comments - one --type comment per anchor cell.
officecli add "$FILE" /Exit --type comment --prop ref=B3 --prop text='Exit EV = Y5 EBITDA × exit multiple'
officecli add "$FILE" /Returns --type comment --prop ref=B3 --prop text='MOIC = exit equity / sponsor equity'
officecli add "$FILE" /Returns --type comment --prop ref=B4 --prop text='IRR - 5-yr, entry + exit only; use XIRR for mid-year dividends'Callout - labels: comment element vs Notes column vs formula (three distinct mechanics).
officecli add ... --type comment --prop ref=<cell> --prop text='...'. The comment key is NOT a valid prop on set cell (not in officecli help xlsx cell on v1.0.63) - it silently drops when embedded inside a set cell props dict. Use the dedicated element.{"command":"set","path":"/DCF/D3","props":{"value":"TV = FCF × (1+g) / (WACC−g)"}} - value, not formula, plain quoted string.{"formula":"FCF10*(1+g)/(WACC-g)"} produces #NAME? in Excel (FCF10, g, WACC are unbound identifiers in that cell context).For mid-year dividends or partial exits, use XIRR({cashflows}, {dates}) instead of IRR.
Step 6 - Returns waterfall (optional, 4-tier LP/GP). Tiers: (1) LP preferred return 8% ; (2) GP catch-up to 20% ; (3) 80/20 split above hurdle ; (4) 100% to LP on loss. Each tier is a MAX(0, MIN(...)) clamp. See §Sensitivity & scenarios for the general grid pattern.
Verification.
officecli get "$FILE" /S&U/B12 --json | jq '.data.value // .data.cachedValue' # must say BALANCED
officecli get "$FILE" /Returns/B3 --json | jq .format.cachedValue # MOIC, expect 2.0x-4.0x typical
officecli get "$FILE" /Returns/B4 --json | jq .format.cachedValue # IRR, expect 0.15-0.30 typical
# Iterate converged?
officecli query "$FILE" 'cell:contains("#REF!")' --json | jq '.data.results | length' # must be 0Three patterns, pick one:
INDEX/MATCH.INDEX/MATCH switch - one validation dropdown on Summary drives every driver via INDEX(Base:Downside, MATCH(Dropdown, ScenLabels, 0)).Mixing (a)+(b) creates circular input (scenario picked by dropdown AND overwritten by Active column) - pick one.
Grid rule: each cell substitutes row-driver and col-driver into a self-contained copy of the output formula. Cannot reference the WACC named range (that's the panel) - reference the grid's axis cell.
Dropdown scenario switch. One validation dropdown on Summary drives every Assumptions row:
cat <<'EOF' | officecli batch "$FILE"
[
{"command":"add","parent":"/Summary","type":"validation","props":{"sqref":"B1","type":"list","formula1":"Base,Upside,Downside"}},
{"command":"set","path":"/Assumptions/B5","props":{"formula":"INDEX(C5:E5,MATCH(Summary!$B$1,$C$4:$E$4,0))"}}
]
EOF
# If you want a hover tooltip on B5, add it separately:
officecli add "$FILE" /Assumptions --type comment --prop ref=B5 --prop text='Revenue growth - picked by Summary!B1 scenario dropdown'Every Assumptions driver row gets the same INDEX/MATCH. Base / Upside / Downside columns on C:E stay blue (hardcoded scenario inputs).
Football-field chart pattern (DCF valuation summary). Horizontal Low→High bars for 3–5 valuation methods (DCF base, DCF bear, Trading comps, Precedent txns, LBO floor) stacked vertically. On a Football sheet: col A = method label, col B = Low $, col C = High $, col D = =C−B (width). Chart as a stacked bar with column B as an invisible first series (white/no-fill) and column D as the visible series - dataRange=Football!A3:D7, chartType=bar. Excel reads this as a floating bar per method.
Terse reference - not a finance textbook. If you don't know what these do, pause and ask the user.
| Function | Prefer over | Why |
|---|---|---|
XNPV(rate, values, dates) | NPV | Irregular cash flow dates (M&A close mid-year, staggered tranches) |
XIRR(values, dates) | IRR | Irregular dates; multiple sign changes handled better |
INDEX(range, MATCH(lookup, key, 0)) | VLOOKUP | Insert-safe (VLOOKUP breaks when a column is inserted in the source range) |
IFERROR(x/y, 0) or IF(y=0, 0, x/y) | bare division | Guard every / in a financial model - #DIV/0! shipped = delivery failure |
MIRR(values, financeRate, reinvestRate) | IRR with sign flips | When cash-flow pattern has 2+ sign changes |
SUMIFS(sumRange, criteriaRange1, criterion1, ...) | SUMPRODUCT((...)) array | Avoids the cached-value trap on array formulas (→ xlsx v2 §Common Workflow Step 5 array-formula fallback) |
SUMPRODUCT(1/COUNTIF(...)) distinct-count trap. The CLI engine caches the inner division per-row → 1/N (e.g. 0.001543) rather than the true distinct count. SUMPRODUCT(--((range<>"")/COUNTIF(range,range&""))) pattern is likewise affected. Fallback (from xlsx v2): hardcode the correct distinct count with a blue font + adjacent comment "hardcoded distinct count; update if rows change", and disclose at delivery. LBO deal-count or portfolio headcount from a transactions list is the typical pattern that hits this.
Cross-sheet NPV() / XNPV() cache-0 fallback (preferred). When the engine caches 0 on a cross-sheet NPV() / XNPV(), replace the formula with its algebraic equivalent SUMPRODUCT(values/(1+rate)^periods) - same result, caches correctly, audits cleanly. This is the first-line fix, used in Recipe B Step 4 by default. For XNPV, the period exponent is (dates - base_date) / 365.
Cache fallback on IRR / MOIC / summary KPI cells (last resort). If a valuation cell still ships with cachedValue = 0 after algebraic rewrite + re-set after close, hardcode the computed value with a blue font and add a classic comment via officecli add "$FILE" /Sheet --type comment --prop ref=<cell> --prop text='cached valuation; refreshes on open in Excel - do not edit'. Disclose in delivery notes. Prefer re-set after close first.
Enable calc.iterate ONLY when circularity is algebraically justified: Interest ↔ Cash (LBO revolver / cash sweep), Tax shield ↔ NI (rare - most 3-statement models compute interest before tax and avoid), Revolver plug ↔ Ending cash (corporate cash waterfall with min-cash).
officecli set "$FILE" / --prop calc.iterate=true --prop calc.iterateCount=100 --prop calc.iterateDelta=0.001iterateCount=100 / iterateDelta=0.001 are Excel defaults, fine for naturally dampening loops.
calc.iterate controls recalc, not write-phase. Appending the closing leg of an already-wired cross-sheet ring (Debt.Interest ↔ CF.Cash ↔ Debt.CashSweep) deadlocks at 100% CPU; view html / get also hang on a non-converged ring.
3-step playbook:
0 (e.g. C7=0, not =-MIN(...)). Removes the ring.set per cell, non-resident.Acceptance. get /Debt/C7 --json | jq .format.cachedValue returns non-zero non-null. If a cell still deadlocks, leave =0 + classic comment "circular; recalculates in Excel on F9", flag at delivery. Never paper over with iterateCount=1000.
Do NOT use iterate as a band-aid for #REF! / divergent values. Raising iterateCount to 1000 hides the bug and ships a plausibly-wrong value; validate does not catch it. Break the loop algebraically (e.g. interest on opening balance only, not average).
Verify convergence. Read the loop cell, bump a driving assumption and back, re-read - values must match:
V1=$(officecli get "$FILE" /Debt/C9 --json | jq .format.cachedValue)
officecli set "$FILE" /Assumptions/B31 --prop value=0.085
officecli set "$FILE" /Assumptions/B31 --prop value=0.0845
V2=$(officecli get "$FILE" /Debt/C9 --json | jq .format.cachedValue)
[ "$V1" = "$V2" ] && echo "Iterate converged" || echo "WARN: drift V1=$V1 V2=$V2 - tighten iterateDelta or check algebra"Assume there are problems. First build is almost never correct. Run every gate below; every check must print its success line. validate passing is not delivery - the model can pass schema and still be wrong by a factor of 10.
→ see xlsx v2 §QA minimum cycle (Gates 1–3 cover view issues, error-cell query, validate after close). Run them first, exactly as written in xlsx v2. No financial-model-specific tweaks.
Balance-check and cash-reconciliation rows produced by Recipe A / C must show OK / BALANCED every period. query the check rows and refuse on any IMBALANCED / CF !=:
BS_FAIL=$(officecli query "$FILE" 'cell:contains("IMBALANCED")' --json | jq '.data.results | length')
CF_FAIL=$(officecli query "$FILE" 'cell:contains("CF !=")' --json | jq '.data.results | length')
SU_FAIL=$(officecli query "$FILE" 'cell:contains("S&U IMBALANCE")' --json | jq '.data.results | length')
if [ "$BS_FAIL" -eq 0 ] && [ "$CF_FAIL" -eq 0 ] && [ "$SU_FAIL" -eq 0 ]; then
echo "Gate 4 OK (balance + recon + S&U all pass)"
else
echo "REJECT Gate 4: BS=$BS_FAIL CF=$CF_FAIL S&U=$SU_FAIL"; exit 1
fiIf any fail, the model is silently wrong - fix the upstream chain before delivery. Most common cause: a cross-sheet formula stored \! (shell-mangled) - run officecli query "$FILE" 'cell:contains("\\\\!")' and re-enter via batch heredoc.
NPV / IRR / XIRR / equity-bridge / MOIC / summary KPI cells cached 0 = wrong number shipped to a reader who does not recalc on open. List every valuation cell and check cachedValue:
# Customize the path list per recipe - this is the DCF example
for P in "/DCF/C4" "/DCF/C5" "/DCF/C6" "/DCF/C8" "/DCF/C9"; do
V=$(officecli get "$FILE" "$P" --json | jq -r '.format.cachedValue // "null"')
if [ "$V" = "0" ] || [ "$V" = "null" ]; then
echo "REJECT Gate 5: $P cached $V - re-set after close (see §Build-order & cache-drift)"; exit 1
fi
echo "Gate 5 $P: cached=$V OK"
doneFor LBO, extend the list: /Exit/B5, /Returns/B3, /Returns/B4. For 3-statement, extend with /Summary/B2:B5.
Every Calc sheet has zero numeric hardcodes. Executable:
HARDCODE=$(officecli query "$FILE" 'cell[type=Number]:not(:has(formula))' --json \
| jq '[.data.results[] | select(.path | test("/(P&L|Balance Sheet|Cash Flow|DCF|Debt|FCF|WACC|Exit|Returns)/"))] | length')
[ "$HARDCODE" -eq 0 ] && echo "Gate 6 OK (no hardcodes on Calc sheets)" || { echo "REJECT Gate 6: $HARDCODE hardcoded numeric cells on Calc zone - move to Assumptions"; exit 1; }
# Named-range coverage + dead-decoration audit: ≥3 ranges declared AND each referenced by ≥1 formula.
NR=$(officecli query "$FILE" namedrange --json | jq '.data.results | length')
[ "$NR" -ge 3 ] && echo "Gate 6 OK ($NR named ranges)" || echo "WARN Gate 6: only $NR named ranges"
DEAD=0
for NR_NAME in $(officecli query "$FILE" namedrange --json | jq -r '.data.results[].name'); do
USES=$(officecli query "$FILE" "cell:has(formula):contains(\"$NR_NAME\")" --json | jq '.data.results | length')
[ "$USES" -ge 1 ] && echo " $NR_NAME: $USES uses OK" || { echo " WARN: $NR_NAME unused"; DEAD=$((DEAD+1)); }
done
[ "$DEAD" -eq 0 ] && echo "Gate 6 named-range audit OK" || { echo "REJECT Gate 6: $DEAD dead-decoration name(s)"; exit 1; }Gates 1–4/6 are grep defenses - they cannot see a rendered sheet. Run officecli view "$FILE" html and Read the returned HTML. Walk every sheet (inherits xlsx v2 visual floor):
### in any numeric cell (widen column).alignment.wrapText=true).TBD, {var}, xxxx) - Gate 6.1 grep below.OK / BALANCED every period column.0 on summary KPIs; if present, run cache-refresh pass.REJECT on any defect. Human preview: officecli watch "$FILE", or open in Excel / WPS / Numbers - final colors + chart fidelity only fully render in the target viewer.
LEAK=$(officecli view "$FILE" text | grep -niE 'TBD|\(fill in\)|xxxx|lorem|\{\{|placeholder|coming soon')
[ -z "$LEAK" ] && echo "Gate 6.1 OK (no placeholder tokens)" || { echo "REJECT Gate 6.1:"; echo "$LEAK"; exit 1; }validate catches schema errors, not finance errors. A model passes validate with BS.Cash hardcoded to force balance, an NPV cached at 0, a sensitivity grid all-zero because it was built before FCF, a #NAME? runtime on a P&L-named sheet with unquoted refs. Gates 4 / 5 / 6 / 5b exist because schema-level validate cannot catch any of this.
→ Base pitfalls (cross-sheet ! trap, batch JSON dotted-name rule, resident + formula batch deadlock, renderer caveats, labelRotation / pareto / databar-min-max bugs, validate while resident): see xlsx v2 §Known Issues & Pitfalls - all apply.
Financial-model-specific:
=-COGS*DaysPayable/365. Wrong sign inflates NWC and flips CF direction. Silent; passes validate.#NAME? not caught by query / validate. A cross-sheet formula referencing P&L!B3 without quoting the sheet name (because & is special) lands at runtime as #NAME?. Always write cross-sheet refs as 'P&L'!B3 - single-quote the sheet name if it contains &, space, (, ), etc. Gate 5b visual check is the only detection.calc.iterate=true iterateCount=100 converges at whatever the cap lands on - even if the true answer is 2× that. Always run convergence verify (§Circular references). Complex LBO rings (multi-tranche debt + sweep + tax shield) may not converge; when cachedValue=0 on a ring cell, use §Write-order surgery.batch with a resident open deadlocks at 100% CPU. Even single set on a ring cell can hang. Fix: close residents, write the ring in two passes per §Write-order surgery. Non-resident single-heredoc is the only safe form.view html. Downstream written in the same sequence as upstream caches 0. Excel resolves on open; HTML preview does NOT. Re-set every downstream non-resident after the chain (§Build-order & cache-drift).NPV() / XNPV() cross-sheet caches 0 on v1.0.63. Rewrite as SUMPRODUCT(values/(1+rate)^periods) - algebraically equivalent, caches correctly. Applied by default in Recipe B Step 4.0. Build FCF + WACC + DCF first, then grid in a separate non-resident batch. Fallback: hardcode blue + comment "hardcoded sensitivity; refresh on assumption change".BS.Cash = CF ending cash always (including Y1: BS.Cash = 'Cash Flow'!B19). Never an independent plug or Assumptions ref - a plugged BS.Cash hides balance errors.Opening Cash = prior period Ending Cash (C17=B19, D17=C19). Independent Y2+ opening-cash inputs silently drift from BS.chartType=waterfall cannot mark total programmatically - use colors= convention (dark = total, medium = positive, red = negative). See help xlsx chart.SharesOut is a formula. =BasicShares + OptionPool × ExerciseAssumption → add a blue-font assumption cell and point the SharesOut named range at the computed cell, not the raw input.When in doubt: officecli help xlsx [element] [--json]. Help is the authoritative schema; this skill is the decision guide for financial-modeling deltas.
© FerroxLabs, AGPL-3.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in src/process/resources/skills/officecli-financial-model of FerroxLabs/wayland.
Open the folder on GitHubat commit 4c030c7
We found 4 copies of this SKILL.md (exact, near-identical or edited) in other folders, from 4 other GitHub owners. This page covers the copy in FerroxLabs/wayland, which our catalogue first saw on October 7, 2026.
Officecli Financial Model next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Officecli Financial Model this skillFerroxLabs/wayland | 608 | 4 repos | ~12k | Automated safety check: Pass | AGPL-3.0 | |
| Dcf ModelWind-Alice/AliceMarket | 130 | 3 repos | ~12k | Automated safety check: Pass | None | |
| Startupblueprintbuildfastwithai/gen-ai-experiments | 785 | — | ~2.2k | Automated safety check: Pass | MIT | |
| Dcf ModelLuciole-Studio/Misaka-Agent | 139 | 2 repos | ~12k | Automated safety check: Pass | Apache-2.0 | |
| Excel Dcf Modelerjeremylongshore/tons-of-skills-marketplace | 2.8k | — | ~593 | Automated safety check: Pass | MIT | |
| Build Modeldaloopa/investing | 489 | — | ~1.3k | Automated safety check: Pass | Apache-2.0 |
Wind-Alice/AliceMarket
Real DCF (Discounted Cash Flow) model creation for equity valuation.
buildfastwithai/gen-ai-experiments
Turn a startup, SaaS, app, developer tool, website, repository, or product idea into an evidence-backed business plan, monetization strategy, pricing architecture, editable 12-month financial model…
Luciole-Studio/Misaka-Agent
Build discounted cash flow valuation workbooks in Excel. An agent skill from Luciole-Studio/Misaka-Agent.
jeremylongshore/tons-of-skills-marketplace
Build discounted cash flow (DCF) valuation models in Excel. An agent skill from jeremylongshore/tons-of-skills-marketplace.
daloopa/investing
Build a multi-tab Excel financial model
ginlix-ai/LangAlpha
Builds or repairs an integrated income statement, balance sheet and cash flow model in Excel with live formulas, supporting schedules, scenarios and a Checks sheet.
FerroxLabs/wayland
Install, start, connect, and troubleshoot visualization companion projects for Aion/OpenClaw, with Star-Office-UI as the default recommendation.
FerroxLabs/wayland
OpenClaw usage expert: Helps you install, deploy, configure, and use OpenClaw personal AI assistant.
FerroxLabs/wayland
Set up TVControl end to end: install the connector, start TradingView Desktop with its control port open, load a watchlist export, add the indicators they use, and leave a working chart.
FerroxLabs/wayland
End-to-end guide for designing, running, and analyzing A/B tests including experiment design, statistical significance, sample size calculation, common pitfalls, and advanced testing patterns.
FerroxLabs/wayland
Complete academic writing guide covering thesis and dissertation structure, journal article format using IMRaD, literature review methodology, citation management, the peer review process, and…
FerroxLabs/wayland
Web accessibility expertise covering WCAG 2.2 conformance, audit methodology, ARIA patterns, keyboard navigation, screen reader testing, focus management, form accessibility, and automated vs manual…
Works with
Categories
A skill your agent uses when the user wants to build a financial model - 3-statement model, DCF valuation, LBO, SaaS unit economics, sensitivity / scenario analysis, debt schedule, or fundraising…. Officecli Financial Model is an agent skill from FerroxLabs/wayland. Use this skill when the user wants to build a financial model - 3-statement model, DCF valuation, LBO, SaaS unit economics, sensitivity / scenario analysis, debt schedule, or fundraising projections - in Excel.
Officecli Financial Model fits situations like: the user wants to build a financial model - 3-statement model; saaS unit economics; sensitivity / scenario analysis; fundraising projections - in Excel.
Run `npx skills add FerroxLabs/wayland --skill officecli-financial-model -a claude-code`. Or copy the skill folder (src/process/resources/skills/officecli-financial-model in FerroxLabs/wayland) into .claude/skills/officecli-financial-model in your project. Claude Code loads it when a task matches its description.
Run `npx skills add FerroxLabs/wayland --skill officecli-financial-model -a codex`. Or copy the skill folder (src/process/resources/skills/officecli-financial-model in FerroxLabs/wayland) into .agents/skills/officecli-financial-model in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add FerroxLabs/wayland --skill officecli-financial-model -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/officecli-financial-model, .gemini/skills/officecli-financial-model, .github/skills/officecli-financial-model and .opencode/skills/officecli-financial-model in your project.
Going by SKILL.md and its folder, Officecli Financial Model needs the command-line tools its instructions call (jq and python3).
SKILL.md names 1 domain. In commands or code: schemas.openxmlformats.org; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Officecli Financial Model is published under the AGPL-3.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 12k tokens (SKILL.md is roughly 48k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Officecli Financial Model: Dcf Model (Wind-Alice/AliceMarket, 130 stars), Startupblueprint (buildfastwithai/gen-ai-experiments, 785 stars), Dcf Model (Luciole-Studio/Misaka-Agent, 139 stars) and Excel Dcf Modeler (jeremylongshore/tons-of-skills-marketplace, 2.8k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
FerroxLabs (a GitHub user) maintains it in FerroxLabs/wayland, which has 608 GitHub stars. The repository holds 22 skills in this directory. The repository was last updated on October 6, 2026.
Source: FerroxLabs/wayland on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.