900×2100 的门洞,展开面积却是 3.4 ㎡ —— 不锈钢核价引擎的七条口径

18 阅读12分钟

给一家不锈钢装饰加工厂写核价系统的过程中,我把"一套门套到底多少钱"这件事拆成了七条口径。每一条都踩过坑,每一条都不是看两眼就能想到的。

文中所有数字来自实际跑通的代码,价表用占位值。不是软文,最后我会说清楚这套东西值多少钱取决于什么。


引子:1.89 ㎡ 和 3.40 ㎡

一个门洞,900 宽、2100 高。

老板接电话时脑子里过的是:0.9 × 2.1 = 1.89 平米。按 304 拉丝一平米多少钱,乘一下,报出去。

但车间真正要下的料,是 3.40 平米。

差了 80%。

这 80% 从哪来的?不是"损耗"两个字能解释的。它来自三件具体的事:

  1. 套线有宽度。门套不是一张平板贴在洞口上,它有正面可见的面宽(比如 60mm),还有折到墙里去的那一段。
  2. 墙有厚度。240 的墙,意味着每道折边要往里折 240mm。这 240mm 是要花钱买板的。
  3. 双包是两面。墙的两面各包一次。

把这三件事摊开,一条料就不再是"门洞的一条边",而是一条宽 319.5mm、长 2175mm 的板。

摊平宽度 Wd = 套线面宽 c + 折边道数 k × 抱墙深度 a + 安装折脚 − 折弯扣减
            = 60 + 1 × 240 + 20 − 0.5
            = 319.5 mm

立边长 Lv = 洞口高 H + 套线面宽 c + 端头余量 = 2100 + 60 + 15 = 2175 mm
横边长 Lh = 洞口宽 W + 套线面宽 c + 端头余量 = 900 + 60 + 15 = 975 mm

单面净展开面积 = (2 × 2175 + 975) × 319.5 / 1e6 = 1.701 ㎡
双包 = 1.701 × 2 = 3.40 ㎡

到这里,第一条口径就出来了。


口径一:计价面积是"摊平展开面积",不是洞口投影

这是所有外行做报价系统的第一个坑,也是最贵的一个。

投影面积(W × H)只能用来对外说"我们按洞口面积计价"这种营销口径。内部算成本必须走展开,因为你要买的是板,不是洞。

两者差多少?实测样例:

门洞投影面积净展开(双包)倍数
900 × 2100,墙厚 2401.89 ㎡3.40 ㎡1.80×

如果按投影算材料,用料面积会从 3.67 ㎡ 变成 2.04 ㎡,少算四成多。而在这个行业里,材料费占总成本的 72%(后面会讲为什么这个数字重要)——总成本会被低估三成左右,报出去的价基本等于白干。

有一个细节容易漏:折弯那道扣减。

折弯要扣中性轴

板子折 90°,折弯处外侧拉伸、内侧压缩,中性轴不变。所以每道折边实际少用半个板厚:

折弯扣减 = 折边道数 k × 0.5 × 板厚 t

1.0 的板每道扣 0.5mm,1.2 的板每道扣 0.6mm。

听起来微不足道。但一条料的摊平宽度也就 320mm 左右,扣 0.5mm 是 0.16%;而如果忘了扣,师傅照着这个宽度下料,折完就短了——整条料报废。

这类"数值上很小、工艺上致命"的参数,是行业软件和通用软件最不一样的地方。


口径二:墙厚 D 不是可选参数,是价格主因

大部分报价模板里,"墙厚"是个备注字段。

在这个模型里,它是主输入。因为墙厚直接决定抱墙深度,而抱墙深度直接进出摊平宽度:

双包:抱墙深度 a = D(一直折到对面)
单包:抱墙深度 a = min(D, 抱墙上限)(单包有上限,超了要配收边条)

240 墙和 120 墙,同一尺寸的门套,展开面积差多少?

  • D=240:Wd = 60 + 240 + 20 − 0.5 = 319.5mm → 3.40 ㎡
  • D=120:Wd = 60 + 120 + 20 − 0.5 = 199.5mm → 2.12 ㎡

面积差 38%,报价能差一倍以上(因为还叠加了折弯、包装、物流的联动)。而客户在电话里说的往往是"就普通门洞"——你必须追问墙厚。

同一个输入参数,在报价系统里的地位是分三六九等的。墙厚属于"不问清楚就没法报价"的那一类。


口径三:成本加成率 30%,实际利润率只有 23.1%

这条不涉及几何,但它是老板最容易算错的一条,而且直接影响他信不信你的系统。

报价 = 成本 × 1.30        ← 这是"成本加成 30%"
利润率 = (报价 − 成本) / 报价 = 0.30 / 1.30 = 23.1%   ← 这才是"销售利润率 23.1%"

他嘴上说"我要 30 个点利润",心里想的和做出来的是两件事。

所以模型里我留了两个开关,二选一:

if margin_rate is not None:      # 他说"我要 30% 利润"
    quote = cost / (1 - margin_rate)
else:                            # 他说"成本加三成"
    quote = cost * (1 + markup_rate)

这两个口径不能混用,也不能悄悄帮他换算——必须让他自己选,界面上写清楚。因为一旦他按 1.3 报出去、回头发现只有 23 个点,他会觉得是软件算错了。

报价系统里,口径歧义比计算错误更伤信任。


口径四:造型改的是几何长度,不只是损耗率

最初我以为"造型"就是给个损耗系数:直边 8%、上圆角 12%、半圆拱 15%。

做完几何展开才发现,造型改变的是件清单本身:

上圆角 R:
  立边 Lv −= R
  横边 Lh −= 2R
  新增 2 段 1/4 圆弧,弧长 = π/2 × R
  → 中心线净变化 = R × (π/2 − 2) = −0.4292 R / 角

注意最后那个负号。

上圆角在面积上是省料的(每角少 0.4292R)。一个 R80 的角,中心线短 34mm。

那为什么加圆角的报价反而更贵?因为贵的地方根本不在板:

  • 滚弯工时(按米计,比直边折弯贵几倍)
  • 12% 而不是 8% 的损耗(滚弯整形容易废)
  • 镜面/镀钛滚弯后要返工抛光,表面费上浮 15%

只算面积、不算工时的人,会把带圆角的活报便宜,然后做完发现不赚钱。

半圆拱:半径不是那个半径

这条我们出过 bug,值得单独说。

半圆拱的真实拱半径是:

R = (洞口宽 W + 横边加长) / 2

而不是参数表里那个 arc_radius。

arc_radius 是"上圆角"造型用的圆角半径(默认 60),半圆拱根本不用它。但两个字段都叫"半径",UI 一排就挨着,结果就是——师傅照着 R60 去滚,实际要 R480。

拱的半径错了,弧长就错了,展开面积、滚弯米数、下料长度全错。这是一个字段名污染了一整条计算链。

修法很土:把两个参数在数据结构上彻底分开,arc_radius 只在上圆角分支里可读,半圆拱分支连它的名字都取不到。


口径五:物流必须"泡货取大",而且顺序不能反

不锈钢重,但门套是空心的、还长。这类货叫泡货——按重量算运费你会亏,按体积算才合理。

规则是三条取最大值:

物流费 = max(体积 × 体积单价, 重量 × 重量单价, 最低一票)

用上面那个 900×2100 / 304 / 1.0 双包的例子:

用料面积 = 3.40 × 1.08(直边损耗)= 3.67 ㎡
重量 = 3.67 × 7.93(不锈钢密度)× 1.0 × 1.10(包装加强系数)= 32.0 kg

打包体积:
  总摊平宽 3 × 0.3195 = 0.96 m
  捆数 = ceil(0.96 / 0.35) = 3 捆
  体积 = 3 × 0.35 × 0.14 × 2.175(最长件)= 0.320 m³

按 180 元/m³ 和 1 元/kg 算:

  • 按体积:0.320 × 180 = 57.6 元
  • 按重量:32.0 × 1.0 = 32.0 元
  • 最低一票:45 元

取大 → 57.6 元。

(成本汇总表里这一项最终是 59 元,差异来自捆数向上取整后的实际体积,量级一致。写这段是为了说明顺序,不是为了对齐到分。)

如果只按重量算,这单运费少算 25 块。单套 25 块听起来不多,但批量 20 套就是 500 块,而且是净亏——运费是实付的。

这里还有个隐藏点:必须先算体积,才能算运费。而体积依赖"最长件长度"和"总摊平宽度",这两个又来自件清单。所以物流费不能提到前面算,也不能拍一个比例。


口径六:七项成本要分项,不能拍一个数

完整的成本链长这样:

# ① 材料
用料面积 = 净展开面积 × 套数 × (1 + 损耗率)
板材费   = 用料面积 × 厚度单价[板厚] × 材质系数[材质]

# ② 折弯(直边和滚弯是两种工时,价格差几倍)
折弯费 = (折边米数 × 折边单价 + 滚弯米数 × 滚弯单价) × 套数

# ③ 焊接(分三种缝,不能一个系数了事)
焊接费 = 拼角数 × 角焊单价
       + 拼板处数 × 拼板单价
       + 拼角数 × 打磨单价 × (摊平宽度/1000)
       + (可选无痕满焊) 展开总长 × 满焊单价
# 满焊无缝:焊缝 = 全部展开总长
# 拼角焊:  焊缝 = 角焊缝 + 接料缝(处数 × 摊平宽)

# ④ 表面处理(弧形/异形上浮 15%——滚弯后镜面要返工)
表面费 = 净展开面积 × 表面单价 × 计价面数 × 弧形难度系数

# ⑤ 包装
包装费 = (单套木架 + 净展开面积/套数 × 每平米护边费) × 套数

# ⑥ 物流(见上)
# ⑦ 安装
安装费 = 单套安装费 × 套数 + (异地差旅)

拿样例 1(900×2100,D240,双包,304 / 1.0 / 拉丝 / 直边)实跑出来的结构:

项金额占比
板材160172.3%
折弯1285.8%
焊接602.7%
表面954.3%
包装1215.5%
物流592.7%
安装1506.8%
总成本2214
建议报价(加成 30%)2879实利 23.1%

这张表最重要的不是金额,是那个 72.3%。


口径七:价表不能写死在引擎里,而且校准有优先级

引擎里只放公式,所有单价从 pricebook 查——引擎代码里一个价格数字都没有。

这不是洁癖。原因很实在:每个厂的底价都不一样,而且同一家厂的板材价一年要变好几次。价格写死在代码里,改一次价就要改代码、重新打包、重新发给客户。

但真正难的不是"把价表抽出来",而是告诉老板先校准哪几项。

回到那个 72.3%:

板材占七成,所以校准优先级是:厚度单价 > 表面单价 > 其余全部。

一个厂校准价表,如果按界面顺序从上往下填,他会在折弯、焊接上花两小时,而这两项加一起才占 8.5%。等填到板材已经没耐心了,随手填个大概——整个系统的准确度就毁在这一步。

所以界面上我做了"置信度"提示:进来先看板材和表面这两项填了没有,没填就标黄、算出来的价打低置信度标记。让老板知道"这个数现在还不能拿去报价"。

双引擎对数:防口径漂移

前端要实时(改一个参数价格立刻跳),后端要权威(出 PDF、存档、算账)。

所以我们跑了两套引擎:JS 的 engineLiteCalculate() 负责实时预览,Python 的 cost_engine.py 负责最终结果。两边公式同源,容差 1e-6,有 7 个用例 × 5 个指标的自检脚本。

这里踩过一个很典型的坑:

对数是拿两边算同一个输入比较结果。但如果 JS 用的是内置演示价表、Python 用的是用户校准后的真实价表,两边结果必然不同——然后自检脚本会报"公式不同步"。

这不是公式不同步,是取数源不同步。

修法是:对数时从本地服务 /api/pricebook 取实时价表,显式传给 JS 引擎;取不到就报错退出,绝不静默退回演示价表。静默兜底是这类工具最坏的设计——它会把"我没取到数据"伪装成"两边算得不一样",让人去查一个不存在的问题。


还有一层:工艺告警

算得准只是及格。这个行业真正的痛点是算得准的活做不出来。

所以引擎跑完会顺手过一遍工艺规则:

条件提示
弯曲半径 < max(25, 20 × 板厚)低于冷弯极限,会开裂
0.6 板 + 摊平宽 > 150mm平整度风险(波浪纹),建议 ≥ 0.8
201 + 镀钛附着力/耐蚀差,建议改 304
摊平宽 > 最大板宽需拼板 N 处,费用与损耗已计入
单包 + 墙厚远大于抱边深度另一面要配收边条或改双包
镜面/镀钛 + 弧形滚弯后需返工抛光,表面费已上浮

这些规则不参与算钱,但能拦住"报完价做完发现是废件"。对一个要给客户盖章的报价单来说,提前报警比算得准更值钱。


收尾:这套东西值多少钱,不取决于公式

七条口径写完,看上去很像那么回事。但我要说清楚一句:

公式决定这个系统"能不能用",价表决定它"值不值钱"。

同一份代码,配上准确的本地价表,能帮老板省下每次报价半小时、避免算错一单亏三天;配上随手填的价表,它输出的就是一份看起来很专业、实际不能用的 PDF。

所以这个系统真正的交付物不是代码,是"让老板在 20 分钟内把自己厂的价表填对"这件事。这也是我目前还没做好的部分。


你可以抄走的三件事

如果你也在给传统行业写报价/核价类工具,这三条跟行业无关,是通用的:

  1. 先找"投影面积 vs 实际用料"这类认知差。 行业里越是"大家都知道但没人写下来"的口径,越是你的护城河。它不在需求文档里,在你跟老板聊天时他随口说的那句"这个不能这么算"。
  2. 把口径歧义显式化,别替用户做决定。 加成率还是利润率、双包还是单包、同城还是异地——每个歧义都做成二选一,而不是猜一个默认值。猜错的代价是用户不再信你的数。
  3. 绝不静默兜底。 取不到实时价表就报错,别退回默认值。一个敢报错的工具,比一个永远有结果的工具更值得托付。

这套系统叫铁尺小匠,跑在 Windows 单机上,不联网。写它的起因很简单:厂里报价靠翻本子和问师傅,而这两件事都不该是报价的瓶颈。