国际运费计费重核算 · 体积重抛重一键算
定位与服务边界
这个 Skill 只回答一个问题:承运商会按多少公斤向你收费。
不报价,不比货代,不算关税,不推荐航线。
它做的是:你已经有了每箱的尺寸和实重,现在需要知道
「体积重是多少、按哪个重量收钱、拼箱各家该摊多少公斤」。
市面上大多数运费计算表的问题不是公式错,而是把抛重系数写死在表里——
空运常见 6000、快递常见 5000、海运按 CBM 与重量择大,各家还会变。
半年后系数变了,没人记得改表,报价就系统性算错。
本 Skill 的反做法是:脚本里一个抛重系数都没有,
必须你用 --divisor 传进来,传不了就报错退出,绝不替你猜。
同理,尺寸单位和重量单位认不出来就判阻断,不猜——
把英寸当厘米会差 2.54 倍,把磅当公斤会差 2.2 倍,
猜错的代价远大于「算不出来」。
三分钟上手
第一步:先确认脚本本身是好的
python scripts/freight_wt.py --selftest
实测输出 selftest 62/62 通过,退出码 0。这一步不要跳过——
它同时验证换算常数、单位识别、缺字段阻断、报告章节编号、退出码封闭。
第二步:跑内置 demo,看清报告长什么样
python scripts/freight_wt.py --demo --divisor 6000 --out ./out
会在 ./out 下一个 _demo_箱子 目录里造 3 个示例箱规并出报告。
demo 故意混入了一个英寸/磅的箱子,所以会正常报出 2 条阻断
(尺寸单位混用、重量单位混用)——这是演示单位检查在起作用,不是脚本坏了。
第三步:跑自己的箱规
一箱一个文件,全部放进同一个目录:
Carton: C-01
Length: 60
Width: 40
Height: 40
Dim Unit: cm
Gross Weight: 8
Weight Unit: kg
Pcs: 10
python scripts/freight_wt.py --path ./箱子 --divisor 6000 --out ./out
支持 .txt / .md / .markdown / .csv / .json / .ini / .properties;
中文键名与中文单位同样认,实测 长 / 宽 / 高 / 尺寸单位: 厘米 / 实重 / 重量单位: 公斤 / 箱数 可正常解析且 0 阻断。
字段名不要求统一写法,下面这些都能认:
| 你要表达的意思 | 认得的写法 |
|---|---|
| 长 | Length L Dim L 长 长度 |
| 宽 | Width W Dim W 宽 宽度 |
| 高 | Height H Dim H 高 高度 |
| 尺寸单位 | Dim Unit Dimension Unit Size Unit 尺寸单位;取值 cm / 厘米 / 公分 / in / inch / 英寸 |
| 实重 | Gross Weight Actual Weight Weight G.W. 实重 毛重 重量 单箱重量 |
| 重量单位 | Weight Unit Wt Unit 重量单位;取值 kg / 千克 / 公斤 / lb / lbs / 磅 |
| 箱数 | Pcs Qty Quantity Cartons Pieces 箱数 件数 数量 |
| 箱号 | Carton Carton No Case No 箱号 编号 |
| 分摊方 | Party Shipper Owner Customer 分摊方 货主 拼箱方 |
数字后面带单位也能剥掉(8kg、70 lb、60cm 都认);千分位逗号会去掉。
箱数不写默认 1 箱,不是 0。
输出契约
每次运行产出两份同前缀文件,写到 --out 指定的目录:
国际运费计费重核算报告-<YYYYmmdd-HHMMSS>.md——给人看国际运费计费重核算报告-<YYYYmmdd-HHMMSS>.json——给程序消费
md 报告章节(编号连续,条件章节自动顺延)
| 章节 | 是否必有 | 内容 |
|---|---|---|
| 一、结论先行 | 必有 | 实重合计 / 体积重合计 / 逐箱取大 / 整票取大,及两种口径差百分之几 |
| 二、逐箱明细 | 必有 | 每箱尺寸、体积 CBM、体积重、实重、本箱计费重、抛重比 |
| 三、拼箱分摊(按体积占比) | 有分摊方才有 | 各方体积占比与分摊计费重 |
| 四、问题清单 | 必有 | 阻断 / 警告 / 提示三级,每条带「怎么改」 |
| 五、扫描范围与漏检边界 | 必有 | 明确写出本工具不覆盖什么 |
| 跳过清单 | 有跳过文件才有 | 列出被跳过的文件与原因,不静默吞掉 |
出现条件章节时编号自动顺延(拼箱分摊 + 跳过清单同时出现即为六章),
已由自检断言「条件章节下编号仍连续」覆盖。
JSON 产物字段(给程序消费,逐字段固定)
顶层:generated_at / input / divisor / counts / aggregate / cartons / allocation / issues
counts:blockwarninfocartonsskippedaggregate:sum_actsum_volper_carton(逐箱取大)whole(整票取大)ngap_pctcartons[]:relcartonpartypcsdims(cm 三元组)vol(cm³)vol_wtact_wtchgratio
—— 均为单箱值,乘pcs才是该规格的总量allocation[]:(分摊方, 体积 cm³, 占比 %, 分摊计费重 kg),按占比降序issues[]:固定 6 键scopelevelcatlinesnippetfix;
level取值封闭为阻断 / 警告 / 提示
issues 一律是 6 元组、级别取值封闭,已由自检结构不变式断言兜住——
数据结构不统一时下游解包不报错、只静默取错值,比崩溃更难查。
检查项清单
一、阻断级(算不出来或算出来没意义)
| 检查项 | 触发条件 | 为什么阻断 |
|---|---|---|
| 缺尺寸(长/宽/高) | 任一维缺 | 算不出体积重,计费重无从谈起 |
| 缺实重 | 实重缺 | 无法与体积重取大 |
| 尺寸单位未声明 / 认不出 | 单位空或不在映射表 | 猜错差 2.54 倍 |
| 重量单位未声明 / 认不出 | 单位空或不在映射表 | 猜错差 2.2 倍 |
| 尺寸不是正数 | 长/宽/高 ≤ 0 | 负数尺寸说明数据抄错 |
| 实重不是正数 | 实重 ≤ 0 | 同上 |
| 箱数不是正数 | 箱数 ≤ 0 | 同上 |
| 尺寸单位混用 | 同票出现 cm 与 in | 混用时体积重没有意义 |
| 重量单位混用 | 同票出现 kg 与 lb | 同上 |
| 分摊比例之和不等于 100% | 合计偏差 > 0.5% | 说明有方体积漏填或重复 |
缺项绝不静默成 0:自检有专门断言 缺项不静默成 0,
缺字段时报告里不会冒出 0.00 这种假装算出来的数。
二、警告级(算得出来,但需要你拍板)
| 检查项 | 触发条件 | 怎么改 |
|---|---|---|
| 计费口径不同会差出钱 | 逐箱取大比整票取大高 5% 以上 | 跟货代书面确认按哪个口径收 |
三、提示级(省钱线索)
| 检查项 | 触发条件 | 怎么改 |
|---|---|---|
| 抛货(体积重远大于实重) | 抛重比 ≥ 1.5 | 压缩包装或改小箱型,能直接降计费重 |
触发与不触发
会触发
- 「这票货体积重怎么算」「抛重系数 6000 / 5000 怎么用」
- 「计费重是多少」「按体积重收还是按实重收」
- 「空运运费按体积算,我这票得算多少公斤」
- 「拼箱运费怎么分摊」「几家拼柜,各家该摊多少公斤」
- 「我这套箱规有没有单位写混 / 少填了什么」
不会触发(非触发边界)
- 「哪家货代便宜」「这个运费报价能不能接」——本工具不报价、不比价,属报价比价类工具
- 「HS 编码归到哪一类」——属归类咨询,本工具不涉及
- 「关税多少、到岸成本多少、报多少价」——属到岸成本/报价测算类工具
- 「选哪个国家出海、我们能不能出海」——属出海准备度诊断
- 「找货代、订舱、报关」——属执行动作,不是计算问题
扫描范围与漏检边界
会读的
- 指定目录下所有
.txt / .md / .markdown / .csv / .json / .ini / .properties
文本文件(子目录递归,跳过__pycache__ / .git / node_modules等) - 也接受
--path直接指向单个文件 - 编码按
utf-8-sig → utf-8 → gbk → utf-16依次尝试,最后才errors=replace
不读的(会计入「跳过」并列出,绝不静默吞掉)
- 其他扩展名的文件(如
.xlsx、图片、PDF) - 空文件
- 解析不出任何字段的文件
- 多行数据行的
.csv(要求一箱一文件)
判不了的(本工具明确不做,也不是缺陷)
- 不内置任何承运商费率、燃油附加费、航线系数,本工具只算重量,不报价
- 抛重系数必须由
--divisor传入;一个都不预设,因为空运/快递/海运各不相同且会变 - 拼箱分摊只按体积;按重量或按票数摊的约定本工具不覆盖,合同优先
- 计费重之外的费用(报关、仓储、偏远地区附加费)不在范围内
- 不判断某箱该不该单独发、不优化装载方案
本报告 0 问题 ≠ 这票货不贵,只代表上面的检查项没命中。
常见错误与应对
| 现象 | 原因 | 怎么改 |
|---|---|---|
报错 需要 --divisor(抛重系数) |
本工具不预设任何系数 | 向货代问清系数再传,如空运 --divisor 6000、快递 --divisor 5000 |
报错 --divisor 必须是数字:abc |
系数里混进了单位或中文 | 只写数字:6000,不要写 6000cm3/kg |
报错 --divisor 必须为正数:0 |
传了 0 或负数 | 抛重系数恒为正;海运按 CBM 择大时改用体积口径,不要用 0 |
报错 --demo 与 --path 不能同时使用 |
两个入口互斥 | 只留一个 |
报错 需要 --out(输出目录) |
没给输出目录 | 补 --out ./out |
报错 没有可核算的箱子规格(共跳过 N 个) |
目录下没有能解析出字段的文件 | 看紧随其后的逐条跳过原因,多半是扩展名不对或键名不在别名表 |
| 报「尺寸单位未声明」但我明明写了 | 单位写法不在映射表 | 用 cm 或 in(或 厘米 / 英寸),别写 CM. 公分 之外的自造写法 |
| 报「尺寸单位混用」 | 同票里既有 cm 又有 in | 先统一单位再算;混用时体积重本来就没意义 |
| 报告里某箱尺寸和体积重显示为横杠 | 该箱尺寸或单位缺失,算不出 | 按「二、逐箱明细」里的箱号回去补 |
| 中文单位(厘米/公斤)解析不出来 | 键名不在别名表 | 用 尺寸单位 / 重量单位 这两个确切键名,取值写 厘米 / 公斤 |
异常与兜底
| 场景 | 行为 | 验证方式 |
|---|---|---|
| 某个箱规文件损坏或解析失败 | 记入「跳过清单」,其余照算,单条坏数据不打断整批 | 混合批次实测:2 份正常 + 1 份坏档 → 箱子 2 份(跳过 1),退出码 0 |
| 目录下全是二进制垃圾 | 不崩溃,报 没有可核算的箱子规格,退出码 2 |
二进制-only 目录实测 |
| 空目录 | 同上,退出码 2 | 空目录实测 |
| 编码猜不出(GBK / UTF-16 混着) | 四级兜底,最后一级 errors=replace 也不抛异常 |
自检断言编码兜底顺序 |
--out 指向一个已存在的文件 |
报 输出目录不可写,退出码 3 |
退出码封闭探针实测 |
--path 不存在 |
报 路径不存在,退出码 2 |
退出码封闭探针实测 |
| 参数缺失 / 非法 | 报明确原因,退出码 2 | 无参数、缺 --out、缺 --divisor 逐项实测 |
有阻断级问题且加了 --fail-on-block |
退出码 2(对 --demo 入口同样生效) |
自检断言覆盖 demo 分支 |
无阻断且加了 --fail-on-block |
退出码 0 | 自检断言覆盖 |
退出码封闭为 0 / 2 / 3:0 成功、2 数据或参数问题、3 输出不可写。
main() 顶层显式捕获异常并转为退出码 2,不会把 traceback 抛给调用方;
SystemExit 单独放行,不会被 except Exception 吞掉。
所有参数校验前置到分支之前——--demo 走的是同一套核算,
不会因为它带内置示例数据就绕过 --divisor 校验。
实战案例(真实跑通,数字为脚本实跑输出)
案例一:内置 demo,3 箱混了英寸/磅
命令:python scripts/freight_wt.py --demo --divisor 6000 --out ./out
结果:箱子 3 份(跳过 0),问题:阻断 2 / 警告 0 / 提示 2
| 箱号 | 箱数 | 尺寸(cm) | 体积重(kg) | 实重(kg) | 本箱计费重(kg) | 抛重比 |
|---|---|---|---|---|---|---|
| C-01 | 10 | 60.0×40.0×40.0 | 160.00 | 80.00 | 160.00 | 2.00 |
| C-02 | 5 | 50.0×30.0×30.0 | 37.50 | 60.00 | 60.00 | 0.62 |
| C-03 | 3 | 101.6×76.2×76.2 | 294.97 | 95.25 | 294.97 | 3.10 |
- 实重合计 235.25 kg,体积重合计 492.47 kg
- 逐箱取大 514.97 kg,整票取大 492.47 kg,两种口径差 4.57%
- C-03 原始写的是 40×30×30 英寸、70 磅,换算后 101.6×76.2×76.2 cm、31.75 kg/箱
- 2 条阻断正是「尺寸单位混用」「重量单位混用」——单位统一后重跑即可消除
手算复核:60×40×40 ÷ 6000 = 16 kg/箱 × 10 = 160 kg ✓;
40 in = 101.6 cm,101.6×76.2×76.2 ÷ 6000 = 98.32 kg/箱 × 3 = 294.97 kg ✓。
案例二:拼箱两方,按体积分摊
输入 3 个箱规文件:A-01、A-03 属甲公司,A-02 属乙公司;
其中 A-03 全程用中文键名与中文单位(长 / 宽 / 高 / 尺寸单位: 厘米 / 实重 / 重量单位: 公斤 / 箱数):
命令:python scripts/freight_wt.py --path ./箱 --divisor 6000 --out ./out
结果:箱子 3 份(跳过 0),问题:阻断 0 / 警告 1 / 提示 1
- 实重合计 120.00 kg,体积重合计 160.00 kg
- 逐箱取大 168.67 kg,整票取大 160.00 kg,差 5.42% → 触发警告「计费口径不同会差出钱」
拼箱分摊(基数取整票取大 160.00 kg):
| 分摊方 | 体积占比 | 分摊计费重(kg) |
|---|---|---|
| 甲公司 | 86.67% | 138.67 |
| 乙公司 | 13.33% | 21.33 |
分摊表按占比降序输出,摊得最多的一方排最前。
中文键名(长 / 宽 / 高 / 尺寸单位: 厘米 / 实重 / 重量单位: 公斤 / 箱数 / 分摊方)与英文键名混用,
实测 0 阻断。
已验证清单(全部 2026-10-05 实跑,未跑的不写)
python scripts/freight_wt.py --selftest→ 62/62 通过,退出码 0- 内置 demo(3 箱)→ 阻断 2 / 警告 0 / 提示 2;逐箱取大 514.97 kg vs 整票取大 492.47 kg,差 4.57%
- 拼箱两方(3 箱)→ 阻断 0 / 警告 1 / 提示 1;分摊 86.67% / 13.33%,合计 100%
- 中文键名 + 中文单位(厘米 / 公斤)→ 正常解析,0 阻断
- 缺字段(缺高、缺实重)→ 判阻断,且不输出任何
0.00假装算出来的值 - 混合批次(2 正常 + 1 坏档)→ 单条失败不中断,
箱子 2 份(跳过 1),退出码 0 - 二进制-only 目录 → 不崩溃,退出码 2
- 空目录 → 退出码 2
--out指向已存在文件 → 退出码 3--divisor缺 / 非数字 / 为 0 / 为负 → 退出码 2,且--demo分支同样校验--demo与--path同时给 → 退出码 2- 换
--divisor结果必须变化 → 已断言,确认参数真被消费 - 有阻断加
--fail-on-block→ 退出码 2;无阻断 → 退出码 0(两个入口均已断言) - 报告章节编号在条件章节(拼箱分摊、跳过清单)出现时仍连续 → 已断言
- 产物无
%%转义残留 → 已断言