我近期正在做的一个东西,是维护和扩展一个保险方面的计算引擎。
根据被保险人的年龄,性别,所选产品等的情况,来计算保费,以及保单利益等,最后产生报表(报表在几页到十几页之间)。
这个引擎采用c语言,部署到ios平台和windows平台。
几十种产品,上百个数据表,中间的计算也很复杂。
以前的计算引擎代码,有十几年历史,除了一些数据表外,各种全是硬编码。
近三十万行代码,一旦出错或改动,只能debug。多么痛苦的事情。
而且最近还要改成为另一个国家用的。于是我要做一个重构(除了重用数据以外,其实是推倒重来...)。
目前已经完成主要部分,还有一些正在编程中。
在这里分享一下基本思路。(待续)
重构引擎的目的
1, 尽量把计算保费和各种数据的步骤和输入输出等,都放在配置文件里。减小开发新保险产品时的代码量(最终减小到零代码量)。
2, 提高开发效率和维护效率100%-200%
3,最终目标:非专业人士在短期培训后,就可以用ipad所见即所得配置计算引擎,配置好后,把配置文件email给开发人员。一个新的保险产品的计算引擎部分就完成了。
以下是设计框图,解释待续。
相当于自己实现一个公式分析器?最早之前在做电信产品的时候,我们的PM设计过类似的一个东西。设计师用IBM的rose设计业务流程图,然后用code去分析Rose的文件(某个旧版本的rose生成的文件好像是普通文本,可以用code直接分析,高版本的就不行了),生成我们自己的业务规则,然后用自己的引擎去跑这一条条的规则。
最后实现的效果就是业务设计人员50%+的改动不需要程序员的参与就可以完成了,而且无需等待部署:)
一,Engine Calculation (引擎计算部分)
这一部分是这个引擎的核心部分。
因为所有的保费和报表计算部分,都可以用excel来模拟(在我们这里叫做mock up),所以我就做了一个excel的解释执行器。
先把excel转成xml,然后用c语言解释执行。
一段excel转的xml:
<tr>
<td type="double" index="A16"><![CDATA[1.0]]></td>
<td type="string" index="B16"><![CDATA[Accidental Death & Dismemberment]]></td>
<td type="expression" index="C16"><![CDATA[IF($C$7="Y","PP","PW")&"I"&K16]]></td>
<td type="expression" index="D16"><![CDATA[VLOOKUP($C$5&"I"&$D$7&C16&$C$6,sc_premium!A3:E546,4,0)]]></td>
<td type="expression" index="E16"><![CDATA[VLOOKUP($C$5&"I"&$D$7&C16&$C$6,sc_premium!A3:F546,5,0)]]></td>
<td type="expression" index="F16"><![CDATA[VLOOKUP($C$5&"I"&$D$7&C16&$C$6,sc_premium!A3:F546,6,0)]]></td>
<td type="expression" index="G16"><![CDATA[TRUNC(E16*F16/$F$14,4)]]></td>
<td type="expression" index="H16"><![CDATA[ROUND($G16*H$13,2)]]></td>
<td type="expression" index="I16"><![CDATA[ROUND($G16*I$13,2)]]></td>
<td type="expression" index="J16"><![CDATA[ROUND($G16*J$13,2)]]></td>
<td type="string" index="K16"><![CDATA[A]]></td>
<td type="string" index="L16"><![CDATA[]]></td>
<td type="string" index="M16"><![CDATA[]]></td>
<td type="string" index="N16"><![CDATA[]]></td>
<td type="string" index="O16"><![CDATA[]]></td>
<td type="string" index="P16"><![CDATA[]]></td>
<td type="string" index="Q16"><![CDATA[]]></td>
</tr>
为了方便开发者查看excel在ios上的执行情况,我在ipad上做了一个excel的ui编辑界面。
最麻烦的地方就是查错(我不希望用debugger),尤其是excel的公式执行时出错。所以我在公式的错误检查上下了不少功夫:
以及公式的单步执行:
这个计算模块已经重构了两次,比较成熟了,经历了10多个保险产品的考验,性能和结果准确性满足要求。
做这个模块时所遇见的问题及其解决方案。
1, 多平台部署时的内存问题。
这个引擎部署在ios上使用时效果很好,但转成dll后部署到windows平台,就会crash.其主要问题在于对string的内存释放时出错。目前虽已经稳定,总觉得string的alloc和释放还有些不规矩。正在考虑用stringtable来整体解决string的alloc和释放。
2, 性能问题
最开始时,这个计算引擎只能把excel表格从头到尾计算一遍来获得结果。
当excel表格很大时(700-1000行以上),整个计算一遍速度就太慢了。可以很明显看到延迟。
现在这个引擎已经能做到局部计算,不用计算全表,只计算所需要的量。根据所求的量的公式,来追溯公式里所需的其他变量(跨表),而其他变量又追溯另外的变量,引起连锁反应而获得此数值。经过这方面的加强,性能有了很大提高。
有些数据的计算,需要大量的相关表格,此时,可以把相关表格的计算结果做成一个表格来使用,就不需要引用那么多表格了。
目前的计算引擎是解释执行的,最近学了一些编译原理,有想法把excel转成汇编语言执行,性能想必又会有提高...
假如 车险产品: 需要根据 车龄 行驶里程 贷款 过户 优惠系数 浮动比率 以及险种依赖 等 因素 这个是不是就基本扛不住了?? 似乎你这个只能适用普通险种 比如旅游 类非车险产品 是吗??
二,Input Data Mapping 和 Output Data Mapping
Input Data Mapping 是把保存在结构体里的用户数据,传递到计算引擎中去。
Output Data Mapping 则是把计算结果传递出来,保存到相应结构体变量,格式化等等规整后,便于传递到服务器端以及报表引擎去。
这一部分别看只是数据的传递,实现起来却是很麻烦的。
实际上我今天还来公司加班,正在对这部分做一次重构。
以前的实现,只是简单的硬编码,写set value/get value的方式,把数据向计算引擎里传,或者传出来。
随着所做的product越来越多(时间紧任务重,半成品的engine也要用上啊),发现了弊病:
1, 每个product大体相同,但set value/get value处却总有些微妙区别,导致要根据产品不同加条件判断,增加输入输出语句。这就又逐渐增加了“垃圾代码”。
2,还有的在输入数据时,还需要engine进行验证,有的要输入整个结构体数组,有的输出时要进行结构体数组的初始化....
林林总总的小而麻烦的地方,导致引擎代码在输入输出部分,补丁片片。
可以想象,一两年后,这里就是个大坑了。
目前正在做的重构是,使用类似hibernate的概念,写一个属性匹配的xml,并动态识别struct的属性,来进行自动的输入输出匹配,与此同时进行属性的验证。
谢谢分享 读了之后大开了眼界 期待更新
不过你的公司允许你把产品的设计思路放出来吗?还是说这是个开源引擎?
如果是开源的话,可以问下source code放在哪里吗?
1, 公式或脚本或程序,解析可以使用 Antlr http://www.antlr.org/
2, 内部数据模型向别的表现形式的转换 比如Data Model =>XML,或Data Model => Excel的 可使用模板库 FreeMarker http://freemarker.org/
3, 不赞同以Excel作为数据模型 数据模型应该结构化,便于程序处理,也便于在各模块或各子系统之间传递 Excel应该理解为根据数据模型产生的用户表现层,也就是 View 层 这部分没能完全理解楼主的意图,如果有理解错误,勿怪
我有时候会用到第三方库,有的因内存或环境限制而不合适,有的很好用。
从头造轮子,可能有以下原因:
1,没有意识到有合适的轮子存在(这个最不应该,是我需要警觉的地方)
2,自己这个轮子需要有太多调整。
3,市面上的轮子太“重”和“胖”了,不适合自己用。
4,运行环境和开发语言等的限制
我在这里面用的parser,不是antlr,但也是借鉴了一个开源代码w3parser,
只因该parser的执行速度很快,错误检查和单步执行做的特别好。是java的,我转成了c,非常好用。
excel转xml,用到java里的很多lib,也很好用。
转眼两年过去了,这个引擎虽不太完善,但已经成功覆盖了所有意外险种的计算,占公司险种的30%,大大降低了险种计算引擎的开发难度。
近日,会重新启动该项目。
此次会写一个桌面应用给保险公司的精算师和开发人员用。计划用C++写(兼容以前的C代码)。
其目的是达到完全不需要写代码,就能配置好意外险险种的计算引擎的程度。
而这个应用,其设置方式并不只适用于意外险,会尝试用于其他险种的计算引擎生成。
加油, 我很想看看 你是怎么处理车险产品的计算引擎 , 因为车险这块保费计算因子比非车险产品的计算因子多好几个数量级,商业险各责任的计算方式也不仅相同 , 我觉得很费劲!