与 Excel 的往返转换
值可以双向流动,结构则一次只朝一个方向传递。本文解释为什么这才是正确的取舍。
“如果我在 Excel 里编辑,.deepcell 会保持同步吗?反过来呢?”
我们每次演示都会被问到这个问题。这问得很对。在我用过的几乎每一款建模工具里,那些“导出到 Excel”的功能都是一扇单向的活板门。你导出、在 Excel 里编辑,然后就分叉了。到了周二,原始文件就已经过时。源文件追踪的逻辑不再具有权威性。导出文件成了一件纪念品。
正因如此,“导出到 xlsx”只是功能页上的一个勾选框,而不是任何人真正信赖的工作流。
DeepCell 的做法不同。两个方向,各自都诚实地交代自己承载了什么。
方向一 —— .deepcell → Excel#
deepcell to-excel acme_dcf.deepcell -o acme_dcf.xlsx --formulas这会生成一个工作簿,其中每一条 Jingwei 公式都被翻译成实时的 Excel 公式,而不是固化的值。用普通的 Excel 打开它——无需加载项、无需插件、无需登录——它的表现就跟你用过的任何 xlsx 一样。同样的重算,同样的 F9,同样的单元格。邮件另一端的人根本不需要知道 DeepCell 的存在。他们拿到的就是一份电子表格。
如果你更想交给别人一份无法改动的快照:
deepcell to-excel acme_dcf.deepcell -o acme_dcf_snapshot.xlsx只含值。适用于投委会材料、审计委员会,或任何不该去重新输入 WACC 的人。
这个方向是简单的那个。文件格式拥有结构,导出只是一次渲染。就跟打印 PDF 一样。
方向二 —— Excel → .deepcell#
这部分是大多数工具选择回避的。我们没有。
Excel Add-in 是一个任务窗格——运行在 Office.js 之内的 React——它直接从你的工作区加载一个 .deepcell,并将其渲染到工作表中。你看到的不是一份导出文件。你看到的就是那个文件本身。
当你编辑一个单元格时,Add-in 的 push() 会把改动发送到 /batch-edit API。服务器重新计算依赖项,返回更新后的值,Add-in 再把它们拉回来。后台的 pull() 每三十秒运行一次,以防别人(或某个智能体)正从网页应用编辑同一个模型。
每一次推送都是一次 git 提交。版本历史不是一项附带功能——它就是你从 CLI 得到的那同一份历史,也是智能体想要弄清某个数字从何而来时所读取的那同一份历史。
诚实的边界#
下面这部分我想说得精确一些,因为这正是大多数产品撒谎的地方:
| 哪些内容会同步 | 方向 |
|---|---|
| 对已有单元格的值编辑 | 双向 |
| 新增行项目、新增期间、新增公式 | 一次一个方向 |
值是双向的。在 Excel 里编辑 Revenue / 2026A / Base / Actual,模型会重算。在网页应用里编辑它,你的 Excel 任务窗格会把新数字拉过来。没问题。
结构则一次只朝一个方向。如果你想添加一个新行项目、一个新财政期间,或一条新的 CalcDef,你要通过文件格式来做——要么用 CLI 手动操作:
deepcell defs add-calc acme_dcf.deepcell \
--name "Operating Margin" \
--formula "=Operating Income / Revenue"……要么请智能体替你完成。无论哪种方式,Add-in 下一次拉取时,新行就会出现在那里。
如果你走的是反方向——在 Excel 里加了一行,想让它回到 .deepcell 里——那就重新导入。我们不会试图通过观察一个新行所处的位置和它的名称去猜测它的含义。
这是一种取舍,而我想为它辩护。Excel 的“约定式 schema”——也就是“这一行是小计,因为它是粗体的;这一列是预测,因为它是斜体的”这种想法——无法干净地反向传播到一个带类型的文件格式中。一个试图这么做的解析器,将不得不替你做出本应由你自己来做的决定。更好的做法是:让文件格式成为声明结构的地方,让 Excel 成为编辑值的界面。更详尽的论证见为什么需要一种新的文件格式。
从 Excel 导入这一侧本身就是另一个故事——在把你的 Excel 模型带进来中有详述。
关于冲突#
如果你在 Excel 里编辑 Revenue / 2027E,而你的同事正在网页应用里编辑同一个单元格,Add-in 会察觉到。它会把你的本地编辑与服务器版本进行比对,并在下一次同步时把冲突暴露出来,而不是悄悄覆盖你们其中一方的改动。
这跟任何一款像样的协作工具所用的模型是一样的。区别在于,我们有地方来安放这个冲突——文件格式拥有带版本的单元格,单元格记录了作者,而冲突的解决会成为又一次提交。这不是什么魔法。只不过是一个带类型的文件格式,给协作层提供了可以抓握的东西。
一个适配器是巧合,两个适配器才是契约。#
简单聊一点架构,然后我就打住。
你在网页应用里看到的网格,是由一个 RenderPlan IR 渲染出来的。你在 Excel 任务窗格里看到的网格,是由同一个 RenderPlan IR 渲染出来的。网页端用 Handsontable,Excel 端用 Office.js——两个适配器,一个中间表示。浏览器里的单元格和任务窗格里的单元格是同一个单元格。它们必须如此。否则,上文所讲的往返转换故事就是个谎言,而两个界面中总有一个在悄悄掩盖文件里真正的内容。
我们曾经有一段时间是逐个界面各做各的。那很糟糕。正是这个 IR,让其余的一切都变得诚实。
共存,而非取代#
DeepCell 活在 Excel 之上,而不是与它为敌。
如果你的日常工作就是 Ctrl-方向键和 =SUMIFS,那就留在 Excel 里。用 Add-in。让 .deepcell 去承载结构、公式、版本历史,以及模型为何被塑造成这般模样背后的推理。大多数时候你不会注意到我们的存在,而这正是关键所在。
如果你的日常工作是在网页应用里——因为你正在与一个智能体协作,或者你在从零开始构建,又或者你单纯就是更喜欢它——那就用网页应用。只在需要把东西发给某个常驻 Excel 的人时,才切到 Excel。
两种工作流都是真实的。两种都没有错。是文件格式让它们成为同一个模型。
亲眼看一看吧——在在线演练场里打开一个示例 .deepcell。编辑一个值,看着依赖项重新计算,并查看任何一个数字背后的推理。