科研技能库/Excel 工作簿作者
科研效率
未发现用户侧风险

Excel 工作簿作者

使用 openpyxl 在无界面环境中构建可审计的 Excel 工作簿——遵循蓝/黑/绿单元格约定、使用公式而非硬编码、命名范围、平衡检查及敏感性表格。适用于财务模型、审计输出和对账。

文件预览

2 个文件
scripts
SKILL.md
9.0 KB · 可预览
---
name: excel-author
description: Build auditable Excel workbooks headless with openpyxl — blue/black/green cell conventions, formulas over hardcodes, named ranges, balance checks, sensitivity tables. Use for financial models, audit outputs, reconciliations.
version: 1.0.0
author: Anthropic (adapted by Nous Research)
license: Apache-2.0
platforms: [linux, macos, windows]
metadata:
  hermes:
    tags: [excel, openpyxl, finance, spreadsheet, modeling]
    related_skills: [pptx-author, dcf-model, comps-analysis, lbo-model, 3-statement-model]
---

# excel-author

Produce an .xlsx file on disk using `openpyxl`. Follow the banker-grade conventions below so the model is auditable, flexible, and reviewable by someone other than the person who built it.

Adapted from Anthropic's `xlsx-author` and `audit-xls` skills in the [anthropics/financial-services](https://github.com/anthropics/financial-services) repo. The MCP / Office-JS / Cowork-specific branches of the originals are dropped — this skill assumes headless Python.

## Output contract

- Write to `./out/<name>.xlsx`. Create `./out/` if it does not exist.
- Return the relative path in your final message so downstream tools can pick it up.
- One logical model per file. Do not append to an existing workbook unless explicitly asked.

## Setup

```bash
pip install "openpyxl>=3.0"
```

## Core conventions (non-negotiable)

### Blue / black / green cell color
- **Blue** (`Font(color="0000FF")`) — hardcoded input a human entered. Revenue drivers, WACC inputs, terminal growth, market data.
- **Black** (default) — formula. Every derived cell is a live Excel formula.
- **Green** (`Font(color="006100")`) — link to another sheet or external file.

A reviewer can then scan the sheet and immediately see what's an assumption vs. what's computed.

### Formulas over hardcodes
Every calculation cell MUST be a formula string, never a number computed in Python and pasted as a value.

```python
# WRONG — silent bug waiting to happen
ws["D20"] = revenue_prior_year * (1 + growth)

# CORRECT — flexes when the user changes the assumption
ws["D20"] = "=D19*(1+$B$8)"
```

The only hardcoded numbers permitted:
1. Raw historical inputs (actual revenues, reported EBITDA, etc.)
2. Assumption drivers the user is meant to flex (growth rates, WACC inputs, terminal g)
3. Current market data (share price, debt balance) — with a cell comment documenting source + date

If you catch yourself computing a value in Python and writing the result, stop.

### Named ranges for cross-sheet references
Use named ranges for any figure referenced from another sheet, a deck, or a memo.

```python
from openpyxl.workbook.defined_name import DefinedName
wb.defined_names["WACC"] = DefinedName("WACC", attr_text="Inputs!$C$8")
# then elsewhere:
calc["D30"] = "=D29/WACC"
```

### Balance checks tab
Include a `Checks` tab that ties everything and surfaces TRUE/FALSE:
- Balance sheet balances (assets = liabilities + equity)
- Cash flow ties to period-over-period cash change on the BS
- Sum-of-parts ties to consolidated totals
- No rogue hardcodes inside calc ranges

Example:
```python
checks = wb.create_sheet("Checks")
checks["A2"] = "BS balances"
checks["B2"] = "=IS!D20-IS!D21-IS!D22"
checks["C2"] = "=ABS(B2)<0.01"  # TRUE/FALSE
```

### Cell comments on every hardcoded input
Add the comment AS you create the cell, not later.

```python
from openpyxl.comments import Comment
ws["C2"] = 1_250_000_000
ws["C2"].font = Font(color="0000FF")
ws["C2"].comment = Comment("Source: 10-K FY2024, p.47, revenue line", "analyst")
```

Format: `Source: [System/Document], [Date], [Reference], [URL if applicable]`.

Never defer sourcing. Never write `TODO: add source`.

## Skeleton: typical financial model

```python
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.comments import Comment
from openpyxl.utils import get_column_letter
from pathlib import Path

BLUE = Font(color="0000FF")
BLACK = Font(color="000000")
GREEN = Font(color="006100")
BOLD = Font(bold=True)
HEADER_FILL = PatternFill("solid", fgColor="1F4E79")
HEADER_FONT = Font(color="FFFFFF", bold=True)

wb = Workbook()

# --- Inputs tab ---
inp = wb.active
inp.title = "Inputs"
inp["A1"] = "MARKET DATA & KEY INPUTS"
inp["A1"].font = HEADER_FONT
inp["A1"].fill = HEADER_FILL
inp.merge_cells("A1:C1")

inp["B3"] = "Revenue FY2024"
inp["C3"] = 1_250_000_000
inp["C3"].font = BLUE
inp["C3"].comment = Comment("Source: 10-K FY2024 p.47", "model")

inp["B4"] = "Growth Rate"
inp["C4"] = 0.12
inp["C4"].font = BLUE

# --- Calc tab ---
calc = wb.create_sheet("DCF")
calc["B2"] = "Projected Revenue"
calc["C2"] = "=Inputs!C3*(1+Inputs!C4)"   # formula, black

# --- Checks tab ---
chk = wb.create_sheet("Checks")
chk["A2"] = "BS balances"
chk["B2"] = "=ABS(BS!D20-BS!D21-BS!D22)<0.01"

Path("./out").mkdir(exist_ok=True)
wb.save("./out/model.xlsx")
```

## Section headers with merged cells

openpyxl quirk: when you merge, set the value on the top-left cell and style the full range separately.

```python
ws["A7"] = "CASH FLOW PROJECTION"
ws["A7"].font = HEADER_FONT
ws.merge_cells("A7:H7")
for col in range(1, 9):  # A..H
    ws.cell(row=7, column=col).fill = HEADER_FILL
```

## Sensitivity tables

Build with loops, not hardcoded formulas per cell. Rules:

- **Odd number of rows/cols** (5×5 or 7×7) — guarantees a true center cell.
- **Center cell = base case.** The middle row/col header must equal the model's actual WACC and terminal g so the center output equals the base-case implied share price. That's the sanity check.
- **Highlight the center cell** with medium-blue fill (`"BDD7EE"`) and bold.
- Populate every cell with a full recalculation formula — never an approximation.

```python
# 5x5 WACC (rows) x terminal growth (cols) sensitivity
wacc_axis = [0.08, 0.085, 0.09, 0.095, 0.10]        # center row = base 9.0%
term_axis = [0.02, 0.025, 0.03, 0.035, 0.04]        # center col = base 3.0%

start_row = 40
ws.cell(row=start_row, column=1).value = "Implied Share Price ($)"
ws.cell(row=start_row, column=1).font = BOLD

for j, g in enumerate(term_axis):
    ws.cell(row=start_row+1, column=2+j).value = g
    ws.cell(row=start_row+1, column=2+j).font = BLUE

for i, w in enumerate(wacc_axis):
    r = start_row + 2 + i
    ws.cell(row=r, column=1).value = w
    ws.cell(row=r, column=1).font = BLUE
    for j, g in enumerate(term_axis):
        c = 2 + j
        # Full DCF recalc formula (simplified for illustration).
        # In a real model this references the full projection block.
        ws.cell(row=r, column=c).value = (
            f"=SUMPRODUCT(FCF_range,1/(1+{w})^year_offset) + "
            f"FCF_terminal*(1+{g})/({w}-{g})/(1+{w})^terminal_year"
        )

# Highlight center cell (base case)
center = ws.cell(row=start_row+2+len(wacc_axis)//2,
                 column=2+len(term_axis)//2)
center.fill = PatternFill("solid", fgColor="BDD7EE")
center.font = BOLD
```

## Recalculating before delivery

openpyxl writes formula strings but does not compute them. Excel recalculates on open, but downstream consumers (auto-check scripts, CI) need computed values.

Run LibreOffice or a dedicated recalc step before delivery:

```bash
# LibreOffice headless recalc
libreoffice --headless --calc --convert-to xlsx ./out/model.xlsx --outdir ./out/
```

Or use a Python recalc helper (see `scripts/recalc.py` in this skill).

## Model layout planning

Before writing any formula:
1. Define ALL section row positions
2. Write ALL headers and labels
3. Write ALL section dividers and blank rows
4. THEN write formulas using the locked row positions

This prevents the cascading-formula-breakage pattern where inserting a header row after formulas are written shifts every downstream reference.

## Verify step-by-step with the user

For large models (DCFs, 3-statement, LBO), stop and show the user intermediate artifacts before continuing. Catching a wrong margin assumption before you've built downstream sensitivity tables saves an hour.

Checkpoint pattern:
- After Inputs block → show raw inputs, confirm before projecting
- After Revenue projections → confirm top line + growth
- After FCF build → confirm the full schedule
- After WACC → confirm inputs
- After valuation → confirm the equity bridge
- THEN build sensitivity tables

## When NOT to use this skill

- Users in a live Excel session with an Office MCP available — drive their live workbook instead.
- Pure tabular data export with no formulas — `csv` or `pandas.to_excel` is simpler.
- Dashboards / charts with heavy interactivity — use a real BI tool.

## Attribution

Conventions (blue/black/green, formulas-over-hardcodes, named ranges, sensitivity rules) adapted from Anthropic's Claude for Financial Services plugin suite, Apache-2.0 licensed. Original: https://github.com/anthropics/financial-services/tree/main/plugins/vertical-plugins/financial-analysis/skills/xlsx-author

SKILL.md

元数据
nameexcel-author
description使用 openpyxl 在无界面环境中构建可审计的 Excel 工作簿——遵循蓝/黑/绿单元格约定、使用公式而非硬编码、命名范围、平衡检查及敏感性表格。适用于财务模型、审计输出和对账。
version1.0.0
authorAnthropic (由 Nous Research 改编)
licenseApache-2.0
platforms["linux","macos","windows"]
metadata{ "hermes": { "tags": [ "excel", "openpyxl", "finance", "spreadsheet", "modeling" ], "related_skills": [ "pptx-author", "dcf-model", "comps-analysis", "lbo-model", "3-statement-model" ] } }

excel-author

使用 openpyxl 在磁盘上生成 .xlsx 文件。遵循以下银行家级约定,以使模型可审计、灵活且可由构建者之外的人审阅。

改编自 Anthropic 的 xlsx-author 和 audit-xls 技能,位于 anthropics/financial-services 仓库。已移除 MCP/Office-JS/Cowork 特定分支——本技能假定为无界面 Python。

输出约定

  • 写入 ./out/<name>.xlsx。若 ./out/ 不存在则创建。
  • 在最终消息中返回相对路径,以便下游工具获取。
  • 每文件仅一个逻辑模型。除非明确要求,否则不向现有工作簿追加内容。

安装

bash
pip install "openpyxl>=3.0"

核心约定(不可协商)

蓝/黑/绿单元格颜色

  • 蓝色 (Font(color="0000FF")) — 人工输入的硬编码。收入驱动因素、WACC 输入、终值增长率、市场数据。
  • 黑色 (默认) — 公式。每个派生单元格都是活页 Excel 公式。
  • 绿色 (Font(color="006100")) — 链接至其他工作表或外部文件。

审阅者可以扫描工作表并立即看出哪些是假设,哪些是计算值。

公式优先于硬编码

每个计算单元格必须是公式字符串,绝不能是 Python 计算后粘贴的数值。

python
# 错误——沉默的 bug 等待发生
ws["D20"] = revenue_prior_year * (1 + growth)

# 正确——当用户更改假设时灵活应变
ws["D20"] = "=D19*(1+$B$8)"

唯一允许的硬编码数字:

  1. 原始历史输入(实际收入、报告的 EBITDA 等)
  2. 用户需要调整的假设驱动因素(增长率、WACC 输入、终值 g)
  3. 当前市场数据(股价、债务余额)——并附上注明来源和日期的单元格注释

如果你发现自己在 Python 中计算值并写入结果,请停止。

用于跨工作表引用的命名范围

对于从另一工作表、演示文稿或备忘录引用的任何数字,使用命名范围。

python
from openpyxl.workbook.defined_name import DefinedName
wb.defined_names["WACC"] = DefinedName("WACC", attr_text="Inputs!$C$8")
# 然后在其他地方:
calc["D30"] = "=D29/WACC"

平衡检查标签页

包含一个 Checks 标签页,用于核对所有内容并展示 TRUE/FALSE:

  • 资产负债表平衡(资产 = 负债 + 所有者权益)
  • 现金流量与资产负债表上各期现金变化相匹配
  • 分部加总与合并总数一致
  • 计算范围内无杂散硬编码

示例:

python
checks = wb.create_sheet("Checks")
checks["A2"] = "BS balances"
checks["B2"] = "=IS!D20-IS!D21-IS!D22"
checks["C2"] = "=ABS(B2)<0.01"  # TRUE/FALSE

每个硬编码输入均添加单元格注释

在创建单元格时即添加注释,而非之后。

python
from openpyxl.comments import Comment
ws["C2"] = 1_250_000_000
ws["C2"].font = Font(color="0000FF")
ws["C2"].comment = Comment("Source: 10-K FY2024, p.47, revenue line", "analyst")

格式:来源:[系统/文档],[日期],[引用],[URL 若适用]。

绝不要推迟来源标注。绝不要写上 TODO: add source。

框架:典型财务模型

python
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.comments import Comment
from openpyxl.utils import get_column_letter
from pathlib import Path

BLUE = Font(color="0000FF")
BLACK = Font(color="000000")
GREEN = Font(color="006100")
BOLD = Font(bold=True)
HEADER_FILL = PatternFill("solid", fgColor="1F4E79")
HEADER_FONT = Font(color="FFFFFF", bold=True)

wb = Workbook()

# --- 输入标签页 ---
inp = wb.active
inp.title = "Inputs"
inp["A1"] = "MARKET DATA & KEY INPUTS"
inp["A1"].font = HEADER_FONT
inp["A1"].fill = HEADER_FILL
inp.merge_cells("A1:C1")

inp["B3"] = "Revenue FY2024"
inp["C3"] = 1_250_000_000
inp["C3"].font = BLUE
inp["C3"].comment = Comment("Source: 10-K FY2024 p.47", "model")

inp["B4"] = "Growth Rate"
inp["C4"] = 0.12
inp["C4"].font = BLUE

# --- 计算标签页 ---
calc = wb.create_sheet("DCF")
calc["B2"] = "Projected Revenue"
calc["C2"] = "=Inputs!C3*(1+Inputs!C4)"   # 公式,黑色

# --- 检查标签页 ---
chk = wb.create_sheet("Checks")
chk["A2"] = "BS balances"
chk["B2"] = "=ABS(BS!D20-BS!D21-BS!D22)<0.01"

Path("./out").mkdir(exist_ok=True)
wb.save("./out/model.xlsx")

带合并单元格的节标题

openpyxl 特性:合并时,在左上角单元格设置值,并单独设置整个范围的样式。

python
ws["A7"] = "CASH FLOW PROJECTION"
ws["A7"].font = HEADER_FONT
ws.merge_cells("A7:H7")
for col in range(1, 9):  # A..H
    ws.cell(row=7, column=col).fill = HEADER_FILL

敏感性表格

使用循环构建,而非为每个单元格硬编码公式。规则:

  • 奇数行/列(5×5 或 7×7)——确保有真正的中心单元格。
  • 中心单元格 = 基准情况。 中间行/列的标题必须等于模型实际的 WACC 和终值增长率 g,以使中心输出等于基准情况下的隐含股价。这是健全性检查。
  • 用中蓝色填充 ("BDD7EE") 并加粗突出显示中心单元格。
  • 每个单元格填入完整的重新计算公式——绝不用近似值。
python
# 5x5 WACC(行) x 终值增长率 g(列)敏感性分析
wacc_axis = [0.08, 0.085, 0.09, 0.095, 0.10]        # 中心行 = 基准 9.0%
term_axis = [0.02, 0.025, 0.03, 0.035, 0.04]        # 中心列 = 基准 3.0%

start_row = 40
ws.cell(row=start_row, column=1).value = "Implied Share Price ($)"
ws.cell(row=start_row, column=1).font = BOLD

for j, g in enumerate(term_axis):
    ws.cell(row=start_row+1, column=2+j).value = g
    ws.cell(row=start_row+1, column=2+j).font = BLUE

for i, w in enumerate(wacc_axis):
    r = start_row + 2 + i
    ws.cell(row=r, column=1).value = w
    ws.cell(row=r, column=1).font = BLUE
    for j, g in enumerate(term_axis):
        c = 2 + j
        # 完整的 DCF 重新计算公式(为说明简化)。
        # 实际模型中会引用完整的财务预测块。
        ws.cell(row=r, column=c).value = (
            f"=SUMPRODUCT(FCF_range,1/(1+{w})^year_offset) + "
            f"FCF_terminal*(1+{g})/({w}-{g})/(1+{w})^terminal_year"
        )

# 突出显示中心单元格(基准情况)
center = ws.cell(row=start_row+2+len(wacc_axis)//2,
                 column=2+len(term_axis)//2)
center.fill = PatternFill("solid", fgColor="BDD7EE")
center.font = BOLD

交付前重新计算

openpyxl 写入公式字符串但不计算它们。Excel 在打开时重新计算,但下游使用者(自动检查脚本、CI)需要已计算的值。

在交付前运行 LibreOffice 或专门的重新计算步骤:

bash
# LibreOffice 无界面重新计算
libreoffice --headless --calc --convert-to xlsx ./out/model.xlsx --outdir ./out/

或使用 Python 重新计算辅助脚本(参见本技能中的 scripts/recalc.py)。

模型布局规划

在编写任何公式之前:

  1. 定义所有节的行位置
  2. 编写所有标题和标签
  3. 编写所有节分隔线和空行
  4. 然后使用锁定好的行位置编写公式

这可以防止在公式编写后插入标题行导致所有下游引用偏移的级联式公式断裂模式。

与用户逐步验证

对于大型模型(DCF、三报表、LBO),在继续之前停下来向用户展示中间产物。在构建下游敏感性表格之前发现错误的利润率假设可以节省一小时。

检查点模式:

  • 完成输入块后 → 展示原始输入,确认后再进行预测
  • 完成收入预测后 → 确认顶线 + 增长
  • 完成自由现金流构建后 → 确认完整的时间表
  • 完成 WACC 后 → 确认输入
  • 完成估值后 → 确认股权价值桥梁
  • 然后构建敏感性表格

何时不使用此技能

  • 用户在实时 Excel 会话中且 Office MCP 可用——改为驱动其实时工作簿。
  • 纯粹的表格数据导出且无公式——csv 或 pandas.to_excel 更简单。
  • 具有高度交互性的仪表板/图表——使用真正的 BI 工具。

署名

约定(蓝/黑/绿、公式优先于硬编码、命名范围、敏感性规则)改编自 Anthropic 的 Claude for Financial Services 插件套件,Apache-2.0 许可。原始地址:https://github.com/anthropics/financial-services/tree/main/plugins/vertical-plugins/financial-analysis/skills/xlsx-author