ARTICLE · 1196809
48 小时,从零手搓一套新能源经济评价软件
## ——一份完整的过程记录,包括所有翻车的地方
10 月 15 日上午,我给自己出了一道题:
不买商业软件、不接现成框架,和 AI 一起,从零做一套能交给新能源投资商用的经济评价软件。
两天后,它跑起来了。
先给硬指标:**15 个计算内核模块、45 条 API 路由、14 个前端页面、14 张符合《建设项目经济评价方法与参数》(第三版)的财务报表;511 项后端测试 + 31 项前端测试全绿;在 Windows 客户机上装完即可使用。**
但这不是一篇"AI 真厉害"的爽文。真正值得记下来的是过程——尤其是那些翻车的地方,以及最后想明白的一件比"能写代码"重要得多的事。
---
## 一、第一天上午:一行代码都没写
反常识的第一步:**整整一个上午,没写一行代码。**
我做的事是"盘问"。把这件事拆成一棵决策树,一个分支一个分支地逼自己回答:目标用户是谁?差异化在哪?指标口径按谁的?交付形态是什么?技术栈选什么?
盘出来的结论,后来每一条都变成了架构里的一个硬约束:
- **目标用户**是新能源投资商、运营商的投资测算岗——他们重 IRR、回收期、敏感性,轻合规。
- **差异化**分三层:136 号文政策口径打底、多方案批量与敏感性自动化主攻、风光储一体化收益做深度。
- **交付形态**是 B/S 私有化部署。理由很实在:客户常有信创要求,桌面端在国产系统上坑太大;而 B/S 把"每个客户端的兼容坑"收敛成"一个服务端 + 一轮前端适配",客户端零安装、刷新即最新版。
- **技术栈**里有一条关键决定:**计算内核只用 Python 标准库,零第三方依赖。**
最后一条值得多说一句。内核是"同一段代码既跑单项目、也跑多方案批量"的核心资产,把 numpy、pandas 引进来,等于把依赖风险和升级不确定性塞进最不该出问题的地方;而且信创环境下,零依赖部署最省事——源码放上去就能跑。
这一步看起来"慢",但它恰恰是后面能快起来的原因。
---
## 二、第二件事也不是写代码,是先写《计算模型说明书》
顺序是刻意的:**先把口径写死,再让代码去对齐。**
为什么?因为这个行业的坑,几乎全部藏在口径里。
举个最典型的例子。行业里普遍以为"光伏和风电的增值税即征即退 50%"还成立。但核对到现行政策才发现:
- 现行**只有海上风电**适用(财政部等 2025 年第 10 号公告,2025-11-01 至 2027-12-31);
- **陆上风电**的那份政策(财税〔2015〕74 号)已自 2025-11-01 废止;
- **光伏**的那份(财税〔2013〕66 号)早已到期未续。
也就是说,如果软件按老习惯"默认给光伏和陆风套用即征即退",结果会**系统性地高估收益**——而且错得不明显,因为算出来的数字看起来很正常。
于是定下一条铁律:**评价规则以第三版为纲,财税口径不一致时以最新政策为准;报表名称沿用第三版原文。**
一句话概括:**名称可以旧,内容必须新。**
还有一处纠偏,结论与直觉完全相反——**退役费**。
直觉上,电站退役要花一大笔钱,理应在使用期内计提。《企业会计准则第 4 号——固定资产》应用指南写明,弃置费用只限于"依国家法律、行政法规、国际公约承担的环境保护和生态恢复义务",并且明确"一般工商企业的固定资产发生的报废清理费用不属于弃置费用"。
而风光电站目前**没有全国性法定强制退役恢复义务**。所以严格按准则,**常规风光项目的退役费恰恰应当"发生时一次计入",而不是计提**。
软件最后把两种模式都实现了,让用户按项目属性选,并在报告上标注口径。这类"反直觉但正确"的结论,恰恰是必须先写文档才能发现的。
---
## 三、内核:零依赖的 15 个模块
规格定了,才开始写内核。
最终是 15 个模块:量纲、参数模型、电量、电价、储能、投资、融资、成本税费、现金流、指标、报表、敏感性、财务计划、资产负债表、编排。
写完拿一个 100 MW 集中式光伏的示例算例跑了一遍,结果是:
**项目投资财务内部收益率 4.68%、财务净现值 -6438 万元、投资回收期 12.29 年、资本金 IRR 6.73%、LCOE 0.3685 元/kWh。**
第一眼看到这组数,我的反应是"模型是不是有毛病"——IRR 才 4.68%,明显低于 8% 的行业基准。
但把口径逐个核对完之后明白了:**这恰恰是 136 号文全面入市之后行业的真实处境。** 光伏实际市场均价平均低于机制电价约 0.127 元/kWh,全行业 IRR 普遍压在 5% 上下。
模型没错。它只是诚实地算出了行业的现实。
这也说明一件事:**一个测算软件的价值,不在于让你看到漂亮的数字,而在于让你看到真实的数字。**
---
## 四、最值钱的一夜:会计恒等式抓出一只"隐身 bug"
如果这两天真有什么"高光时刻",是这一段。
在补两张财务报表(财务计划现金流量表、资产负债表)时,我给测试加了一条约束:**会计恒等式必须在所有情况下配平**(资产 = 负债 + 所有者权益)。
然后,在 24 组参数矩阵里,23 组配平,**只有一组炸了——差额 7604 万元。**
问题配置是:折旧年限(15 年)小于运营期(20 年)。
根因很简单:代码在运营期的**每一年**都计提折旧,没有在折旧年限期满后停下来。而资产负债表里的累计折旧已经封顶在(原值 − 残值)。于是利润表多扣了费用、资产侧已封顶,两边对不上。
**但这个 bug 的可怕之处在于:它不影响 IRR。**
现金流量表里根本不含折旧,所以 IRR 对折旧政策免疫。此前 71 项测试、全部敏感性分析、示例算例,**全都毫无察觉地通过了**——因为既有测试里 `depreciation_years` 全是默认值,从来没覆盖过这条路径。
这件事让我记住了一条教训,现在写在文档和代码注释里:
> **一个只验证"目标指标"的测试套件,必然漏掉"不影响目标指标的错误"。**
>
> 要发现这类错误,必须引入一个"本该处处相等"的**独立约束**。
在这个项目里,那个独立约束就是会计恒等式。
所以财务报表不是"附加功能"——它是**内核对账工具**。
---
## 五、第二程:API 层,挖出 6 个真缺陷
内核稳了之后,往上做服务层和 API 层:45 条路由、框架无关的契约、两个可替换的 HTTP 适配器。
这一段挖出 6 个真缺陷,每一个都值得单独讲,这里挑两个。
**其一,只读闸门死锁。**
授权到期后系统进入只读模式,而"导入新授权"本身也是写操作——于是被闸门挡住了。结果就是:客户**永远无法通过导入新授权来恢复**,只能去改数据库。
修法是给这一条接口开唯一的豁免口,并写一条断言守住"**有且仅有一条豁免**"。因为闸门不能挡住解除闸门的那把钥匙,但豁免口也不能有两个。
**其二,提示信息被静默丢弃。**
系统算出了一堆该告诉用户的话——授权降级提示、参数库冻结提示、"此参数包未经复核,不得用于对外报告"——但**全部没有写进响应**,用户一条都看不到。
抓出它的是两条断言:"用户应当被告知这件事"。
如果没有这两条断言,这个缺陷会一直躺在那儿,直到某个客户拿一份"未经复核"的参数包做了几个亿的投资决策。
还有一处特别典型:FastAPI 通道返回 422,**潜伏了整个第一版都没被发现**——因为环境里一直没装 FastAPI,那条用例一直处于"跳过"状态。
由此定下一条纪律:**跳过不等于通过。**
---
## 六、第三程:前端与真实后端联调
前端(Vue3 + Element Plus + ECharts)做完之后,我们没有停在"类型检查通过、构建成功",而是做了一件更重要的事:
**让前端的接口层,原样去打一个真的后端进程。**
真进程、真数据库、真密钥、真授权文件、真角色分工。跑完那条链:登录 → 建组织 → 建项目 → 建方案 → 补参数 → 跑测算 → 看报表 → 敏感性。
第一轮 8 项失败。逐个查下来:**7 项是测试自己写错了,1 项是真缺陷。**
真缺陷那条也很典型:报表接口返回的是**列表**,而前端按**字典**取值——于是下载页面上**一张表都显示不出来**。它不报错、不变红、类型检查也看不出来。
更值得记的是:**后端自己的契约测试,反而把错误的形状锁住了。** 两侧各自都写了意图,而只有一侧能在运行期成立。
由此又得到一条加测试的纪律:**先问"这一侧凭什么",而不是先把红的改成绿的。**
---
## 七、交付收口:一条"承诺了未实现能力的注释"
软件"能算、能存、能看"之后,做了一次专门的交付盘点——不写新代码,只读代码和文档找差距。
这次盘点挖出一个 P0 级的问题:**厂商公钥没有注入机制。**
具体是:源码里硬编码了一把"演示公钥",而它的**私钥种子就明文写在同一个文件里**;同时,打包脚本里**没有任何一步**把厂商公钥注入进去。
后果是:**客户拿到交付包,用包里那把种子就能自签一份"永久 + 全模块"的授权**——整套收费模式的前提"私钥绝不出厂",当场不成立。
更微妙的是,同一个文件的注释里早就写着"真实部署时由打包流程把厂商公钥写入"——
**而这个流程不存在。**
> 一条承诺了未实现能力的注释,比单纯的缺失更危险:它会让后来者以为这块已经收尾了。
后来怎么修的:把演示密钥从交付内容里彻底删除;兜底方向从"没配置就回退到演示公钥"改成"没配置就一律验签失败、系统只读"——宁可"忘了配置"表现为"用不了",也不能表现为"谁都能签一份授权";并把"交付产物里不含签发种子"写成一条**扫描实际装船清单**的断言,而不是靠构建脚本记得去删。
靠人记得的步骤,迟早会漏。
---
## 八、最后一公里:在真机上装一遍
到这一步,文档齐了、测试绿了,但还有一个问题:**没在真机上装过。**
我找了一台 Windows 电脑当客户机,从零装了一遍。果然卡住了——授权文件导入时验签失败,报"license_format 必须是整数,得到 NoneType"。
排查的过程挺有意思。先做的是**排除法**:在开发机上把那个授权文件用 Python 完整读一遍——字节编码正常、字段是整数、私钥推导出的公钥与公钥文件完全配对、甚至"重新签发再验签"的闭环都跑通了。
**结论是:文件、密钥、签发逻辑,全都是好的。**
那报错从哪来?继续查下去,真相是:**客户机上压根没有公钥文件**,后端启动时也没配公钥——于是验签链条还没走到"比对签名"那一步就失败了。
而那个"NoneType"的报错,是因为当时导入的**不是文件原文**——中间还夹着一个"PowerShell 用 GBK 读 UTF-8、显示成乱码"的假象,让一份好文件看起来像坏的。
配上公钥、改用"选择文件"导入,授权一次通过。
这一公里告诉我们:从"文档齐全"到"真的能装",中间隔着的,就是这些琐碎但真实的东西。
---
## 九、复盘:AI 到底能做什么
两天,一套可交付的行业软件。说三点真实感受。
**第一,AI 把"实现"的成本压到了极低,但"决策"的成本没变。**
盘问决策树、拍板口径、识别行业陷阱、判断一个缺陷该不该修、密钥怎么保管——这些自始至终都是人的事。AI 的价值在于:一旦你给出决策,它能以极高的密度把它落成一整套自洽的代码、测试和文档。
**第二,把约束写成"可执行的断言",是这件事能成的关键。**
"核心层不许 import 外部依赖"不是写在文档里的呼吁,是一条扫描源码的测试;"会计恒等式必须配平"不是一句原则,是 24 组参数矩阵下的硬断言。
**AI 一定会犯错,但错误能被结构性地抓住,才是可行性的来源。**
**第三,过程的价值大于结果。**
这份记录里最有用的部分,不是"我们做出了什么",而是那些翻车的地方——折旧 bug、列表对字典、0/1 对 boolean、只读死锁、被静默丢弃的提示、承诺了却没实现的注释。
**这些坑不挑工具,用不用 AI 都会遇到。** 区别只在于:你有没有一套机制,让它们在交付之前暴露出来。
48 小时之后,我最大的收获不是那套软件,而是想明白了一件事:
> 决定一套软件能不能交付的,从来不是写代码有多快,而是**你有没有把"什么叫做对"定义得足够清楚,并且让你的机器能自动验证它。**
(完)















































































