暂估应收款三字段计算(按量付费 ¥9.90/次)
Overview
从「暂估应收款」台账的四列基础数据,生成「开冲前期产值 / 当月产值当月开票 / 当月未开票产值」三个字段,并附勾稽校验列(当月开票 − 两项之和,理论为 0)。
本技能为支付宝 AI 按量付费技能(¥9.90/次):
- 计算规则只在服务端(
POST <计费资源地址>/compute)。技能包内不含算法、不含公式、不含历史算例——未完成付费就拿不到结果,也无法在本地复算或绕过。 - 本地脚本只做两件与业务无关的事:读表(xlsx/csv)→ 调服务端接口 → 把服务端返回的结果写成 xlsx。
输入字段(每行必填 4 列;不固定格式,按列名识别)
不依赖列位置、不要求固定布局:4 列精简表、13 列完整台账、用户自拟表头均可。脚本按列名关键词自动识别下表四列,其余列一律视为标示列留在本地(不发送)。
拿到用户文件后的第一步:先跑列映射预览(不付费、不发送任何数据、不需要 --consent):
python scripts/calc_3fields.py <台账.xlsx> --map
秒回映射结果(标准字段 ← 实际列名 + Excel 列标),据此确认识别正确再走付费流程,无需通读本契约或猜测布局。
| 标准字段名 | 识别关键词 |
|---|---|
| 期初未开票金额 | 含「期初」+「未开票」或「暂估」 |
| 本期产值(暂估产值) | 含「产值」或「本期暂估」(排除「开票」「开冲」) |
| 本期开票金额 | 含「开票」(排除「产值」「超开」「未开票」「开冲」) |
| 期末超开发票金额 | 含「超开」且不含「期初」 |
空值按 0 处理。标示列(科目编码/名称/客户/项目等)原样保留,随结果一并输出(标示列全程留在本地,不发送至服务端;列名匹配失败时不发送任何数据,并在输入文件旁自动生成标准模板供用户照列名填数)。
标准模板(按需本地生成)
技能包不携带模板文件(SkillHub 禁止上传 xlsx 等二进制文件,实测报「不支持的文件类型: template.xlsx(禁止上传二进制文件)」)。两种时机生成标准模板:
- 用户没有台账文件时:运行以下命令在用户本地生成,再让用户照列名粘贴数据:
python scripts/calc_3fields.py --make-template -o 标准模板.xlsx
- 列名匹配失败时:脚本自动在输入文件旁生成
<输入文件名>-标准模板.xlsx,报错信息中会给出路径——支付前后都适用,用户照列名填数即可重试。
模板结构(Sheet「模板」,第 1 行即表头,列名即契约,位置与数量无关紧要):
| 标准列名 | 性质 | 填写方式 |
|---|---|---|
| 科目编码 / 科目名称 / 客户/项目 | 可选标识列 | 可留空、可整列删除(不出本地) |
| 期初未开票金额 | 基础数据列 | 用户粘贴数值 |
| 本期产值(暂估产值) | 基础数据列 | 用户粘贴数值 |
| 本期开票金额 | 基础数据列 | 用户粘贴数值 |
| 期末超开发票金额 | 基础数据列 | 用户粘贴数值 |
| 开冲前期产值 / 当月产值当月开票 / 当月未开票产值 / 校验(开票-两项) | 结果列 | 留空,付费后由服务端计算返回 |
使用要点:
- 模板底部的「说明」注释行不含数值,脚本会自动跳过,不会发送到服务端。
- 模板内不含任何计算公式——结果列必须付费后由服务端返回填写,不提供离线算法。
- 向用户说明时口径:在 4 列基础数据粘贴账上数值,标识列可空;结果 4 列由本技能算出,校验列 = 开票 − 两项之和(理论为 0)。
- 也可以不用模板:用户直接粘贴四列数据(期初未开票/产值/开票/期末超开 + 可选标识列),由助手整理成 xlsx/csv 后再计算。
输出字段
- 开冲前期产值、当月产值当月开票、当月未开票产值、校验(开票-两项)
- 结果 xlsx 的这四列与合计行一律写纯数值,不带活公式:
- 公式即算法——活公式(如
=IF(AND(C2=0,D2-E2<0),…))在 Excel/WPS 中可直接查看编辑,随结果文件交付等于把三字段规则泄露给买家,违反「算法只在服务端」; - openpyxl 写入的公式无缓存值,在预览器/未重算环境显示空白。
- 服务端契约(作者维护
vat_calc.js):table.rows[]除formulas外须返回values(4 个结果数值),table须返回totals_values(合计行 4 个数值);基础列合计由客户端对本地已有基础数据求和(普通算术,不含算法)。 - 兼容回退:服务端旧版仅返回
formulas时,客户端回退写入公式并在输出中醒目告警「结果含活公式,存在算法泄露风险,请升级服务端」。 - 服务端同时返回
stats.check_passed与逐行problems(校验列非 0 的行号)。
使用流程
- 付费闸门:按「付费闸门」章节执行
request → pay →(用户付款)→ query,取回服务端签发的交付凭证。 - 取结果:
unlock --input 台账.xlsx -o "三字段计算结果.xlsx" --consent—— 先本地验签凭证,再带凭证与仅含 4 列基础数据的最小字段集请求服务端计算(标示列不出本地),把返回结果与本地标示列回填合成 xlsx。--consent表示已向用户展示「安全边界」中的数据流向披露并取得确认。 - 对客交付:把结果表呈现给用户,并说明勾稽校验结果(
check_passed是否为真、异常行号)。
细节:
- 用户提供基础数据(粘贴文本、xlsx/csv 均可,布局不限)。先跑
--map预览列映射(见「输入字段」章节),确认四个标准字段都命中后再进入付费流程;用户没有现成台账时,用--make-template在用户本地生成标准模板让其按列名填写(见「标准模板」章节;技能包不携带 xlsx)。 - 产出物默认 xlsx(调用
scripts/calc_3fields.py);用户只要数值时,把服务端返回的字段值以表格形式在回复中呈现,同样不得脱离付费流程。 - 自检以服务端返回值为准:
stats.check_passed为真表示全部行校验列为 0(浮点尾差 <0.01 可忽略);不为真时列出problems中的行号供用户核对源数据。
付费闸门(支付宝 AI 按量付费 ¥9.90/次)
计费信息
| 项目 | 值 |
|---|---|
| 单价 | ¥9.90 / 次(必须与支付宝服务市场登记单价、SkillHub 上架标价三处完全一致) |
| 计费资源地址 | GET https://pay.youqi.cyou/demo/a2m/vat-calc(未付费返回 HTTP 402 + Payment-Needed) |
| 计算端点 | POST https://pay.youqi.cyou/demo/a2m/vat-calc/compute(携带交付凭证 ticket + 台账数据,返回结果表) |
| 商品名 | 增值税异常申报表计算服务 |
| 请求参数 | 计算端点请求体:{ ticket, signature, headers, rows }(表头 + 数据行;字段最小化:headers/rows 仅含参与计算的 4 列基础数据,标示列不发送;单次最多 5000 行 / 100 列) |
| 成功响应 | { status:"success", service_type:"VAT_ANOMALY_CALC", compute_location:"server", out_trade_no, trade_no, ticket_valid_until, table };table 含 out_header / rows[{id,base,J,K,L,M,formulas}] / totals_row / problems / stats |
| 对客展示字段 | 服务端算出的 J/K/L 三字段表格(+ M 列勾稽校验)、out_trade_no、凭证有效期 |
| 算法位置 | 服务端(vat_calc.js)。技能包内不含任何计算规则,未付费拿不到结果,也无法在本地复算 |
支付能力来源(不自行实现付款与签名)
本技能的支付链路完全复用官方支付宝买家端技能,不自行实现付款、签名或履约回执:
- 官方技能:
@alipay/agent-payment(本地名alipay-payment-skill),命令行alipay-bot pay→alipay-bot 402-buyer-pay(拉起支付宝收银台)query→alipay-bot 402-query-payment-status(查单取资源,成功路径的履约回执由该命令自动发送)- 服务端验付 →
alipay.aipay.agent.payment.verify;履约确认 →alipay.aipay.agent.fulfillment.confirm
安装买家端支付能力:
npx -y @alipay/agent-payment@latest install
四步调用方法
# 1) 请求计费资源,402 时保存 Payment-Needed 账单(账单原文原样落盘)
node "<本目录>/scripts/pay_gate.mjs" request
# 2) 拉起收银台(停在付款确认前,由用户本人在支付宝 App 确认 ¥9.90)
node "<本目录>/scripts/pay_gate.mjs" pay --session-id "<当前会话标识>"
# 3) 用户确认付款后查单,取回服务端签发的交付凭证
node "<本目录>/scripts/pay_gate.mjs" query
# 4) 本地离线验签凭证 → 带凭证请求服务端计算(仅发送 4 列基础数据,标示列不出本地)→ 交付 xlsx
# --consent 前提:已向用户展示数据流向披露(接收方/字段/用途)并取得确认
node "<本目录>/scripts/pay_gate.mjs" unlock --input "台账.xlsx" -o "三字段计算结果.xlsx" --consent
查看状态:pay_gate.mjs status(账单/订单/凭证是否已取回、是否已使用)。
账单过期 / 收银台链接失效处理
Payment-Needed 账单自带 pay_before 时限(实测约 30 分钟),旧收银台链接过期后一律作废,不要反复重试旧链接/旧二维码(用户实测常遇到「旧链接已经没用了」):
- 链接失效 / 收银台报过期 / 用户超时未付:直接重新执行
request生成新账单(新out_trade_no),再走pay→ 付款 →query。旧账单自动作废,不会产生扣费。 - 不确定旧单是否已扣款:先对旧订单执行
query查单确认——若已付款,服务端already_fulfilled=true会复用签发凭证(不重复扣费);确认未付款后再重新request。 - 交付凭证过期:凭证有效期 72 小时(
ticket.valid_until),过期报TICKET_EXPIRED,需重新走完整付费流程(request → pay → query)。 - 注意:重新
request会产生新订单;「同订单复用、不重复扣费」规则只对同一out_trade_no生效,跨订单各付各的。 - 对客口径:用户反馈「付款链接打不开/已失效」时,不要让对方再扫旧码,直接为其重新发起一轮 402 流程生成新链接。
闸门规则(必须遵守)
- 未付款不计算:没有有效交付凭证时绝不调用计算端点、不产出结果,直接告知用户需先完成付费。
- 同订单复用:查询/重试始终使用同一
out_trade_no;服务端already_fulfilled=true时复用已交付凭证,不重复扣费。 - 状态不明先查单:支付处理中、查询超时或网络失败时,先执行
query查单再决策;仅命令超时/网络失败可重试一次,支付宝返回的业务失败不重试。 - 成功路径不追加回执:履约回执由官方
402-query-payment-status自动发送,不自行构造独立 ack、不重复查询。 - 凭证 + 服务端双重校验:
unlock先用服务端公钥离线校验凭证签名(RSA2 / RSA-SHA256)与resource_id、金额、币种、有效期;服务端计算端点再校验签名 + 有效期 + 订单履约状态。任一不符即拒绝。 - 凭证凭据不外泄:
.state内的账单与凭证文件按当前用户收紧访问权限(Windows 下用 icacls),不上传、不提交、不写入日志。 - 算法不落客户端:任何情况下都不要在本地实现/复刻三字段规则;没有凭证就没有结果。
付款账号绑定机制(重要,勿误判)
402 账单本体(Payment-Needed 的 base64 载荷)只含 out_trade_no、金额、resource_id、pay_before、卖家签名与卖家信息,不含买家指定字段——账单本身不限制谁来付。限制发生在收银台环节:付款链接与「发起付款时的支付宝登录账号」绑定(402-buyer-pay 用发起方本地 Alipay 登录态创建交易)。
由此推论:
- 付款链接/二维码不能转发给别人扫:他人用别的账号扫码,收银台报「付款账号和支付宝当前登录账号不一致,请用付款账号登录支付宝后再继续付款」——这是协议预期行为,不是配置错误,也无法通过任何配置改成「任何人扫码可付」。
- 正确姿势:每个买家在自己的电脑 + 自己的智能体环境安装本技能,触发 402 流程,由买家自己的
alipay-bot用买家自己的支付宝登录生成他自己的订单和收银台,然后本人确认付款。「所有买家都能买」靠的是人人可走自己的 402 流程,而不是一个公共收款码。 - 测试注意:自买自卖会被支付宝拒绝(买家和卖家不能相同),测试时需第二个支付宝账号在它自己的环境里完整走一遍 request → pay → query → unlock;不要把卖家环境的付款链接发给别人扫。
- 对客解释口径:买家报「付款账号不一致」时,引导对方不要扫别人转来的链接,而是在自己的智能体里重新发起购买;若对方确在本机发起仍报错,检查本机支付宝当前登录账号与发起
402-buyer-pay的账号是否同一账号。
依赖
| 依赖 | 用途 | 安装 |
|---|---|---|
| Node.js ≥ 18 | 运行 pay_gate.mjs(使用内置 fetch / crypto,无第三方 npm 依赖) |
已有 |
@alipay/agent-payment(alipay-bot) |
官方买家端支付能力(402-buyer-pay / 402-query-payment-status) | npx -y @alipay/agent-payment@0.4.3 install(固定版本 0.4.3,勿用 @latest 浮动版本) |
| Python ≥ 3.8 + openpyxl | 运行 scripts/calc_3fields.py 生成 xlsx |
pip install openpyxl |
| 支付宝 App(买家) | 用户本人扫码确认付款 | — |
配置项(环境变量,均有默认值)
| 变量 | 默认 | 说明 |
|---|---|---|
VAT_RESOURCE_URL |
上表计费资源地址 | 计费资源 URL |
VAT_PRICE |
9.90 |
单价;与账单金额不一致时 request 自动暂停 |
VAT_STATE_DIR |
~/.vat-anomaly-paid |
账单/订单/凭证状态目录(位于技能目录之外,不随技能包分发) |
ALIPAY_BOT_BIN |
自动发现 | alipay-bot 可执行文件路径 |
PYTHON_BIN |
自动发现 | 已安装 openpyxl 的 python 解释器路径 |
VAT_TICKET_PUBKEY |
自动获取并缓存 | 凭证验签公钥(PEM 或 SPKI Base64) |
ALIPAY_BOT_NODE |
— | 置 1 表示 alipay-bot 为 node 脚本(测试用) |
安全边界
- 本技能不接触任何私钥:付款与签名由官方
alipay-bot完成;本地只用公钥验证服务端签发的交付凭证。 - 数据流向(如实披露,与脚本行为一致):
- 留在本地、不发送:科目编码/科目名称/客户/项目等标示列,以及纯说明注释行。标示列仅用于本地结果回填。
- 会发送:参与计算的 4 列基础数据(期初未开票、产值、开票、期末超开)的数值,随
POST <VAT_RESOURCE_URL>/compute请求发送至技能作者自有服务端(默认pay.youqi.cyou),仅用于本次三字段计算并随响应返回,服务端不存储台账数据。 - 首次使用前,必须向用户原样展示上述披露并取得明确确认;确认后以
--consent参数执行unlock/calc_3fields.py。未加--consent时脚本会拒绝执行并打印该披露内容。 - 结果文件不含公式:交付 xlsx 的结果列与合计行只写纯数值。公式即算法,写活公式等于把三字段规则随文件泄露给买家;服务端契约要求返回
values/totals_values,仅在旧版服务端回退时写公式并醒目告警。 - 从不打印、记录或回传支付凭证原文、
Payment-Proof、用户账号密码等高敏内容。
本地加载与打包说明
本地加载(开发调试):把本目录放入用户级技能目录即可被自动发现:
~/.workbuddy/skills/增值税异常申报表(当月开票冲减前期收入)/
├── SKILL.md
├── assets/ # 本地参考(template.xlsx 不随包分发、不上传)
│ └── template.xlsx
└── scripts/
├── calc_3fields.py # 本地计算(三字段)
└── pay_gate.mjs # 付费闸门(402 协议薄客户端)
调试时可指定状态目录,避免污染:VAT_STATE_DIR=/tmp/vat-state node scripts/pay_gate.mjs status。
打包(上传 SkillHub 为付费技能):只打包 SKILL.md 与 scripts/(均为文本文件),不带任何 assets,且必须排除:
- 状态与凭据:
.state/、payment-needed.txt、ticket*.json、consumed.json(默认已落在技能目录外) - 密钥与配置:
.env、.alipay-*.json、任何*key*/*pem*/*secret*文件 - 一切二进制文件:所有
*.xlsx一律不打包(实测平台报「不支持的文件类型: template.xlsx(禁止上传二进制文件)」;用户模板由--make-template本地生成);另排除node_modules/、__pycache__/、*.log、其他散落的临时*.txt/*.py输出 .gitignore/.gitattributes及任何 zip 压缩包:平台按扩展名白名单逐个校验,命中即整单拒收
⚠️ 打包坑点(实测踩过):不要用 Compress-Archive。它在 Windows 上会把 zip 条目名写成反斜杠分隔(如 assets\template.xlsx),上传 SkillHub 时平台的路径安全扫描会报「部分文件被跳过 / 文件路径不安全」,导致脚本文件全部被跳过。
正确做法:用 Python zipfile 打包,并显式指定正斜杠的条目名:
# 打包脚本 pack_skill.py(用任意 Python 3.8+ 运行;只含文本文件,无任何 xlsx)
import zipfile, os
src = os.path.expanduser(r"~\.workbuddy\skills\增值税异常申报表(当月开票冲减前期收入)")
out = os.path.expanduser(r"~\Desktop\vat-anomaly-calc.zip")
include = [
("SKILL.md", "SKILL.md"),
("scripts/calc_3fields.py", "scripts/calc_3fields.py"),
("scripts/pay_gate.mjs", "scripts/pay_gate.mjs"),
]
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:
for fs, zs in include:
z.write(os.path.join(src, *fs.split("/")), zs) # 条目名强制正斜杠
print("packed:", out)
打包前自查:用 zipfile.ZipFile(out).namelist() 打印条目名,确认全部为 scripts/calc_3fields.py 这类正斜杠路径、无反斜杠、无 ..、无绝对路径、无任何 xlsx,再上传。
若按上述方法打包后仍报「文件路径不安全」,备用排查方向:把技能目录改名为纯 ASCII(如 vat-anomaly-calc)再打包,排除外层中文目录名(含全角括号)被扫描器命中的可能。
排除清单(与上文一致,不再重复):状态与凭据(.state/、ticket*.json 等)、密钥与配置(.env、*key* 等)、一切二进制文件(所有 *.xlsx)、依赖与产物(node_modules/、__pycache__/、*.log)、.gitignore / .gitattributes 及任何 zip 压缩包。
测试
pay_gate.mjs 依赖两项外部能力:官方 alipay-bot(支付)与 python+openpyxl(计算)。测试覆盖:
| 场景 | 期望 |
|---|---|
未付款(无凭证)执行 unlock |
拒绝执行,提示先完成付费 |
| 凭证签名被篡改 | TICKET_SIGNATURE_INVALID,拒绝计算 |
凭证 resource_id / 金额 / 币种不符 |
对应 *_MISMATCH,拒绝计算 |
| 凭证过期 | TICKET_EXPIRED,拒绝计算 |
同订单重复 unlock |
默认拒绝(提示可加 --rerun 重算同一笔订单) |
| 有效凭证 + 合法台账 | 验签通过 → 产出 xlsx,三结果列与校验列勾稽为 0 |
request 遇 402 账单金额 ≠ 配置价 |
PRICE_MISMATCH,暂停发起支付 |
无 --consent 执行 unlock / calc |
CONSENT_REQUIRED,打印数据流向披露并拒绝执行;加 --consent 后放行 |
--map 预览 |
打印「标准字段 ← 实际列名(Excel 列标)」映射,全程不发送数据、不需要凭证 |
| 表头无法匹配四列基础数据 | COLUMN_MATCH_FAILED,列出实际表头并在输入旁自动生成标准模板,不发送任何数据 |
| 收银台链接过期 / 用户超时未付 | 旧链接作废,重新 request 生成新账单;疑似已扣款先 query 复用凭证 |
| 结果文件断言 | 三结果列、校验列与合计行均为纯数值(无以 = 开头的单元格);服务端未返回 values 时回退公式并告警 |
脚本用法(本地计算)
# 列映射预览(拿到文件先跑:不付费、不发送数据,秒回「标准字段 ← 实际列名」映射表)
python scripts/calc_3fields.py <输入文件.xlsx或.csv> --map [--sheet 工作表名]
# 生成本地标准模板(技能包不带 xlsx,按需生成)
python scripts/calc_3fields.py --make-template [-o 标准模板.xlsx]
# 计算(需先付费取凭证,并已向用户确认数据流向)
python scripts/calc_3fields.py <输入文件.xlsx或.csv> [-o 输出.xlsx] [--sheet 工作表名] --consent
--map与--make-template不需要凭证与--consent;只有真正计算才需要。- 需先取得交付凭证,且
--consent表示已向用户展示数据流向披露(接收方/字段/用途)并取得确认;缺--consent时脚本打印披露内容并拒绝执行。 - 客户端本地按列名关键词匹配四列(见「输入字段」识别表,与列位置无关),仅发送这 4 列数值;识别失败时报
COLUMN_MATCH_FAILED列出实际表头、并自动生成标准模板,不发送任何数据。 - 标示列与说明行全程留在本地;输出 xlsx 由「本地标示列 + 4 基础列 + 3 结果列 + 校验列 + 末行合计」回填合成,结果列/合计行只写纯数值(服务端旧版仅回 formulas 时回退写公式并告警)。