财务团队每个月都在重复同一套苦活:拼 MBR(月度经营回顾)、组装 Reporting Pack、搭 Variance Bridge、跑 Model Check、做 Planning Scenario。数据来源散落在 Excel、ERP、BI 工具里,格式不统一,人肉拼接极易出错。Codex 的思路是——把这些流程变成代码驱动的可复现任务,让 agent 从真实工作输入中自动生成结果,人只做审核和决策。
Codex 在财务场景里到底做什么
Codex 不是"替你写一段 VBA"的代码补全工具。它是一个可以接收任务描述、读取文件、执行代码、输出结果的 agent。财务团队用它,核心价值在于三点:
- 输入标准化:把散乱的 Excel、CSV、API 数据统一读进 Python 脚本,不再依赖"某个人记得哪个单元格是哪个数字"。
- 流程可复现:每月跑同一套脚本生成 MBR 和 Reporting Pack,差异可追溯,不再靠手工复制粘贴。
- 情景快速迭代:改一个参数就能生成新的 Planning Scenario,不用重新搭整张模型表。
下面按具体场景拆开看。
MBR 与 Reporting Pack:从散装数据到结构化输出
传统做法是把各业务线的数据手动填进模板,格式对齐、单位换算、历史对比全靠人。用 Codex,你可以给 agent 一个任务:
"读取
data/目录下所有业务线本月 CSV,按config/mbr_schema.yaml的结构生成 MBR 数据包,输出到output/mbr_2025_07.json。"
Codex 会自己写脚本读文件、按 schema 聚合、输出结构化结果。人只需要检查 schema 定义和最终数字是否合理。
Variance Bridge:自动拆解差异来源
Variance Bridge 是财务分析里最耗精力的部分——实际与预算的差异要拆到价格、量、混合效应等多个驱动因子。手工拆容易漏项、算错符号。交给 Codex 的好处是:拆解逻辑一旦写成代码,每月自动跑,逻辑透明可审计。
Model Check:给模型加一道自动校验
财务模型越复杂,越容易藏硬伤——公式引用错行、假设参数过期、边界条件没覆盖。Codex 可以跑一套校验脚本,检查模型输出的合理性范围、关键比率是否在历史区间内、输入参数是否与最新假设一致。
Planning Scenario:参数驱动,一键出多版
情景规划的本质是"同一套逻辑,不同参数"。用 Codex,你定义一个 scenario 配置文件,agent 就能批量生成乐观/基准/悲观三版预测,输出对比表和关键指标汇总。
实战:用 Codex 任务跑一套 Variance Bridge + Scenario
下面给一个可以直接改造运行的项目结构示例。假设你已安装 OpenAI Codex CLI(npm install -g @openai/codex),项目目录如下:
finance-codex/
├── data/
│ ├── actual_2025_07.csv
│ ├── budget_2025_07.csv
│ └── assumptions.yaml
├── config/
│ └── mbr_schema.yaml
├── scripts/
│ └── variance_bridge.py
│ └── scenario_runner.py
├── output/
└── prompts/
│ └── variance_task.md
第一步:定义任务 prompt
prompts/variance_task.md:
# 任务:生成 2025年7月 Variance Bridge
1. 读取 `data/actual_2025_07.csv` 和 `data/budget_2025_07.csv`。
2. 按产品线和区域维度,计算实际与预算的总差异。
3. 将总差异拆解为:量差(volume)、价差(price)、混合差(mix)。
4. 拆解公式参考 `scripts/variance_bridge.py` 中的逻辑。
5. 输出 JSON 结果到 `output/variance_bridge_2025_07.json`,格式与 `config/mbr_schema.yaml` 中 variance 部分一致。
6. 如果某行数据缺失或格式异常,跳过该行并在输出中记录 warning。
第二步:核心计算脚本
scripts/variance_bridge.py——这是 Codex 会参考和执行的逻辑,你也可以直接手动运行调试:
"""
Variance Bridge 计算:量差、价差、混合差
输入:actual CSV 与 budget CSV,列名需包含 product, region, units, revenue
输出:JSON 格式的差异拆解结果
"""
import csv
import json
import yaml
from pathlib import Path
def load_csv(path: Path) -> list[dict]:
rows = []
with open(path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
try:
row["units"] = float(row["units"])
row["revenue"] = float(row["revenue"])
rows.append(row)
except (ValueError, KeyError):
print(f"WARNING: skip malformed row in {path}: {row}")
return rows
def variance_bridge(actual: list[dict], budget: list[dict]) -> dict:
"""按 (product, region) 聚合,拆解差异"""
def aggregate(rows):
result = {}
for r in rows:
key = (r["product"], r["region"])
result.setdefault(key, {"units": 0.0, "revenue": 0.0})
result[key]["units"] += r["units"]
result[key]["revenue"] += r["revenue"]
return result
a = aggregate(actual)
b = aggregate(budget)
bridge = {}
total_variance = 0.0
for key in b:
a_val = a.get(key, {"units": 0.0, "revenue": 0.0})
b_val = b[key]
b_price = b_val["revenue"] / b_val["units"] if b_val["units"] else 0
a_price = a_val["revenue"] / a_val["units"] if a_val["units"] else 0
volume_var = (a_val["units"] - b_val["units"]) * b_price
price_var = (a_price - b_price) * b_val["units"]
mix_var = a_val["revenue"] - b_val["revenue"] - volume_var - price_var
bridge[f"{key[0]}|{key[1]}"] = {
"volume_variance": round(volume_var, 2),
"price_variance": round(price_var, 2),
"mix_variance": round(mix_var, 2),
"total_variance": round(a_val["revenue"] - b_val["revenue"], 2),
}
total_variance += a_val["revenue"] - b_val["revenue"]
bridge["_summary"] = {"total_variance": round(total_variance, 2)}
return bridge
def main():
data_dir = Path("data")
output_dir = Path("output")
output_dir.mkdir(exist_ok=True)
actual = load_csv(data_dir / "actual_2025_07.csv")
budget = load_csv(data_dir / "budget_2025_07.csv")
result = variance_bridge(actual, budget)
with open(output_dir / "variance_bridge_2025_07.json", "w", encoding="utf-8") as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"Done. Total variance: {result['_summary']['total_variance']}")
if __name__ == "__main__":
main()
运行前准备两份 CSV,列头至少包含 product, region, units, revenue:
product,region,units,revenue
WidgetA,North,1200,36000
WidgetA,South,800,24000
WidgetB,North,500,50000
WidgetB,South,300,30000
直接手动测试:
cd finance-codex
python scripts/variance_bridge.py
# 输出: Done. Total variance: xxx
cat output/variance_bridge_2025_07.json
第三步:用 Codex agent 执行任务
cd finance-codex
codex --task prompts/variance_task.md --full-auto
--full-auto 让 Codex 自动读取文件、执行脚本、输出结果。你审核 output/ 下的 JSON 即可。
第四步:情景规划扩展
data/assumptions.yaml 定义多版参数:
scenarios:
base:
growth_rate: 0.05
price_increase: 0.02
optimistic:
growth_rate: 0.10
price_increase: 0.05
pessimistic:
growth_rate: -0.02
price_increase: 0.00
scripts/scenario_runner.py:
"""基于 assumptions.yaml 的多情景预测生成"""
import csv
import json
import yaml
from pathlib import Path
def apply_scenario(budget_rows: list[dict], params: dict) -> list[dict]:
result = []
for row in budget_rows:
new_units = row["units"] * (1 + params["growth_rate"])
old_price = row["revenue"] / row["units"] if row["units"] else 0
new_price = old_price * (1 + params["price_increase"])
result.append({
"product": row["product"],
"region": row["region"],
"units": round(new_units, 2),
"revenue": round(new_units * new_price, 2),
})
return result
def main():
data_dir = Path("data")
output_dir = Path("output")
output_dir.mkdir(exist_ok=True)
budget = []
with open(data_dir / "budget_2025_07.csv", newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
row["units"] = float(row["units"])
row["revenue"] = float(row["revenue"])
budget.append(row)
assumptions = yaml.safe_load(open(data_dir / "assumptions.yaml", encoding="utf-8"))
all_scenarios = {}
for name, params in assumptions["scenarios"].items():
projected = apply_scenario(budget, params)
total_revenue = sum(r["revenue"] for r in projected)
all_scenarios[name] = {
"params": params,
"projected": projected,
"total_revenue": round(total_revenue, 2),
}
with open(output_dir / "scenarios_2025_07.json", "w", encoding="utf-8") as f:
json.dump(all_scenarios, f, indent=2, ensure_ascii=False)
for name, data in all_scenarios.items():
print(f"{name}: total_revenue = {data['total_revenue']}")
if __name__ == "__main__":
main()
python scripts/scenario_runner.py
# base: total_revenue = xxx
# optimistic: total_revenue = xxx
# pessimistic: total_revenue = xxx
落地前想清楚几件事
- 数据质量是前提:Codex 能自动跑流程,但输入数据脏,输出也脏。先花时间统一 CSV 格式和列名规范,再让 agent 上场。
- schema 先定再跑:
mbr_schema.yaml是你和 agent 的契约。结构不定,agent 生成的格式每次都可能漂移,审核成本反而更高。 - 从单点切入:别一口气把 MBR + Bridge + Scenario 全交给 agent。先从 Variance Bridge 这种逻辑明确、输入固定的场景开始,验证流程可靠后再扩展。
- 人审不可省:Codex 输出的数字必须与已知口径交叉验证。agent 替你算,不替你担责。
- 版本控制输出:
output/下的 JSON 按月份命名归档,配合 git 管理,任何月度的数字都能回溯。
财务团队用 Codex 的本质不是"AI 替我做表",而是把反复出现的分析流程变成代码 + 配置驱动的可复现 pipeline。手工拼接的月报永远有"这次忘了改那个链接"的风险;代码跑出来的月报,风险只在于你有没有审过输出数字。