天启查 · 企业工商信息报告
技能版本 2.8.0 | 变更历史见 CHANGELOG.md
产出单文件 HTML 深色驾驶舱报告:零外部依赖、手写内联 SVG、每个数字可追溯、每个结论标注证据强度。
如何召唤
三种召唤方式
| 方式 | 怎么用 | 说明 |
|---|---|---|
| 自然语言(最省事) | 直接说「帮我出一份 XX 公司 的工商信息报告」 | 命中本技能 description 里的关键词即自动加载 |
| 斜杠命令(最确定) | /tianqi-enterprise-report |
按技能显式点名,不依赖语义匹配 |
| 点名 + 交代要素 | /tianqi-enterprise-report <标的公司工商全称>,用途:投前尽调,要股权+经营+风险 |
一次把要素给全,可省掉一轮追问 |
触发词(自然语言里出现任意一个即可命中):
企业工商信息报告、企业查询报告、股权穿透报告、股东结构、实际控制人、关联方、尽职调查、背景调查、尽调报告、企业风险报告、工商信息、股权结构、企业族群、天启查。
> 技能刚装好时,当前会话的可用技能列表还没刷新 —— 重开一个会话才会出现在斜杠命令候选里。自然语言触发不受此影响。
直接可复制的召唤语
帮我出一份<标的公司工商全称>的工商信息报告
/tianqi-enterprise-report <标的公司工商全称>
用途:投前尽调
重点:股权穿透、实际控制人综合持股、关联方与关联交易、诉讼风险
数据截止:最新
把这个文件里的企业资料整理成一份可视化的企业工商信息报告:<文件路径>
上次那份 XX 公司的报告,帮我更新到最新一期数据,并重新跑一遍完备性自检
召唤时最好交代这 6 件事
交代得越全,返工越少(尤其第 3 项 —— 它决定哪些节要写)。没说时技能会按阶段 0 主动追问。
| # | 要素 | 为什么要 |
|---|---|---|
| 1 | 标的主体(工商全称,或简称+城市) | 重名企业极多,全称才能定位;有曾用名一并给 |
| 2 | 统一社会信用代码 | 消歧的唯一依据,也是检索式第 ② 路的必备项 |
| 3 | 用途 | 尽调 / 合作核验 / 竞品研究 / 风险排查 —— 按 05-立案与分岔.md 决定节组合与详略 |
| 4 | 数据截止时点 | 会写进报告的「数据口径」标签,与抓取日期分开记 |
| 5 | 重点维度 | 股权 / 经营 / 风险 / 业务基本面 —— 不指定则全做 |
| 6 | 已知线索 | 已掌握的公告编号、文件路径、股东名单 —— 能省掉一整轮检索 |
它交付什么
一次调用产出以下几样,缺一不算完成:
- 立案卡 —— 标的类型(T1–T4)、预估数据等级(L1–L5)、据此成立的节与删掉的节
- 报告 HTML —— 单文件、零外部依赖、双击即开
原始出处/目录 —— 原件 PDF + 全文提取 + 逐字摘录(含公告编号、日期、页数、取件地址)- 取证完备性自查表 —— 10 项,实际填写并在回复里展示
- 交付前自检 ——
scripts/selfcheck.py全通过 - 四视口测量结果 ——
measure.py硬失败必须为 0,且figcheck.py零互叠(前者带阈值,后者是它的零阈值补集,两个都要) - 分区块截图 —— 逐块目视确认过。这一步不能省:可静态判的显示事故已经交给
measure.py、figcheck.py与selfcheck.py,剩下两类只有肉眼能抓 ——
① 合法渲染但结果是错的(日期被折成两行、text-overflow对 flex 容器无效导致硬裁);
② 语义矛盾(同一个量在两节写成不同的数、分类说明的合计小于其总数)。
逐块看时按这三条问:同一量在不同节的数值是否同值?分类说明合计是否等于总数?
有没有哪个数、哪个日期、哪个标签看起来怪?
★ 目视必须按「整页缩略 → 局部放大」两步走,只看缩略图会误读。
分区块截图是整页高度导出后按视口宽度显示,密集图区的标注文字会被压到 0.7 倍左右,
汉字在这种倍率下会糊成互相像的字,从而凭空"看"出并不存在的内容。
实测踩过:把某图里的一行普通行标误读成一条高亮的注文("增资 87 倍"),再去源码里查证,
发现整份报告根本没有这句话 —— 白跑一轮,且如果不是去查证,就会把一条幻觉当成缺陷去修。
纪律:凡是要对某个数、某个标签下结论的地方,先用 PIL 按原生分辨率裁出局部
(必要时 1.6–2 倍放大)再看第二眼;截图原图一般是 1440×N 或 1600×N,
而显示倍率约为 0.72 与 0.65,换算时先乘回去。
下结论前先问一句:这个字是我看清的,还是我猜的?
它不会做什么
- 不编数、不估算:取不到就写「未见披露」,绝不用「约 / 左右 / 推算」填空格
- 不把推算值写成披露值:推算值一律标注「按上述数据计算所得,非披露原文」
- 不采信转载页标题:财务数据必须回到原件核对公告编号,并做反向验证留痕
- 不沉默降级:数据等级低(拿不到 A 级财务)时照做,但必须显式声明缺哪一类数据,
不能删掉几节就当报告写完了
铁律
> 「能查到多少」不等于「有多少」。
> 任何一类数据,在写进报告之前都必须回答一句:为什么可以认为它已经取全了?
> 答不出来,就不许当完整 —— 必须显式标注「未穷尽 / 未见披露」。
这条铁律来自一次真实事故:某报告只取了最新一期公告,拿到了 2023–2025 三年财务数据,看起来很完整、勾稽也自洽,但漏掉了 2022 年 —— 因为那个数据的披露位置在下一年的公告里。内部一致性检查永远发现不了这种漏洞,只有「上溯一期 + 数字反向检索」能发现。
> 「我查到了什么」不等于「读者想知道什么」。
> 节序是读者的提问顺序,不是取证的执行顺序:① 这是家什么公司 → ② 它怎么样 → ③ 你凭什么这么说。
> §01 业务与基本面 回答 ①,位置固定不许后移;§08/§09 与页脚属方法层,靠后放。
实测(2.1.0 的由来):第一版报告把「业务基本面」排在倒数第二节,读者要读满约 50,000 字
才第一次知道「这家公司是做 AIGC 电商直播的」;而方法层(口径核对 11,590 字 + 历史沿革 13,682 字)
占了全报告 29%。报告的重心一旦落在「我怎么查的」,它就从一份企业情报变成了一份工作日志。
> 判据:把 §02 之后最长的三节字数相加,超过全报告 25% 就是方法层吃掉了事实层。
工作流(阶段 S 收费闸门 + 五阶段;阶段 S 与阶段 2 都不可跳过)
阶段 S · 收费闸门(技能一启动就跑,先于立案)
计费发生在这一步,不在成稿交付时。 技能被召唤后,做的第一件事不是检索,而是过闸门:
| 步 | 动作 | 结果 |
|---|---|---|
| S1 | call_paid_api.py -s "<标的全称>" -u "<用途>" --dry-run |
打印账单(单价 / 计费单元 session.start / 有效期),退出码 3 = 需要付款 |
| S2 | 把账单原样交给用户确认付款 | 用户付款,运行时给回 Payment-Proof |
| S3 | call_paid_api.py -s "…" -u "…" -p <凭证> --session-out session.json |
验凭证 → 结算(本次计 1 次)→ 发开工许可 session_id |
| S4 | 拿到开工许可后,才进入阶段 0 立案 | 后续取证、成稿、返工、重跑验收全都不再计费 |
--dry-run那趟不扣钱,只是问价。用户拒绝付款就到此为止,不会产出一份「查了一半」的报告。- S4 之前不许开始检索取证。 白干一次的代价是真实成本,不是一句「反正没收钱」。
- 交付阶段的 402 一律当故障处理(客户端退出码 4,拒付)—— 计费点被挪回交付就是 bug,见下节。
- 详细条款、幂等规则与三趟命令,见
付费层(Pay Skill)。
阶段 0 · 立案(先定「能为这个标的拿到什么」)
按 references/05-立案与分岔.md 写出立案卡,贴进回复。
这一步的输出不是「我知道了标的叫什么」,而是这份报告该保留哪几节。
| # | 字段 | 必填性 | 决定了什么 |
|---|---|---|---|
| 1 | 标的主体工商全称 | 必填 | 重名企业极多;同时给曾用名与统一社会信用代码 |
| 2 | 统一社会信用代码 | 强烈建议 | 消歧的唯一依据,检索式第 ② 路必备 |
| 3 | 报告用途 | 必填 | 按 05 第三节的用途→节组合路由表定必选/加重/可删 |
| 4 | 数据截止时点 | 必填 | 写进页脚数据口径。必须与「抓取日期」分开记 |
| 5 | 重点维度 | 可选 | 股权 / 经营 / 风险 / 业务。不指定则全做 |
| 6 | 已知线索 | 强烈建议 | 已掌握的公告编号、文件路径、股东名单 —— 能省掉一整轮检索 |
然后必须判定两件事并写下来:
- 标的类型 → 证据链(
05第一节,按序问、命中即停):
T1 自身上市 / T2 发债或新三板挂牌 / T3 非上市但有上市关联方(借道关联交易公告,lag = 1)/ T4 以上都不是。 - 预估数据等级 → 降级路径(
05第二节):
L1 完整(≥4 期连续)/ L2 可用(2–3 期或有断年)/ L3 单薄(仅 1 期)/ L4 无 A 级财务但有工商年报 / L5 仅基础工商字段。
L4/L5 不等于「报告做不出来」 —— 是换一套指标:财务侧改为「经营侧写」(参保人数序列 + 资产状况档),
并按等级写对应的降级声明(.callout warn)。沉默降级是最严重的误导:
读者会以为「这家公司就这么多数据」,而真相是「这一类数据本来不公开」。
> 判据:报告里每一个「没写」的地方,读者都要能看出是「没有」还是「没查」。
阶段 1 · 取证
按 references/01-取证规程.md 的四维矩阵展开,不要凭直觉查。
四个维度都必须显式枚举:
| 维 | 要点 |
|---|---|
| 主体 | 标的 / 母公司 / 子公司 / 参股 / 曾用名 / 已注销 / 实控人其他企业 / 关键自然人 —— 8 类一个都不能少 |
| 时间 | 先定「起始锚点」与「披露滞后 lag」,再定应得期数;序列不允许断年 |
| 载体 | 法定披露 / 工商公示 / 司法 / 监管 / 媒体 —— 至少覆盖 3 类 |
| 字段 | 每个关键字段先展开异名表再检索(「净亏损」vs「净利润 -X」、「经营范围」vs「主营业务」…) |
检索必须走五路召回,其中第 ④ 路是防漏网的关键:
① 精确全称:《企业全称》 工商信息 股东
② 主体别名:全称 OR 简称 OR 曾用名 OR 统一社会信用代码
③ 载体定向:site:cninfo.com.cn 企业全称 关联交易 2023
④ 数字反向:拿已确认的一个数字去搜 ← 反查出同系列还有哪些期
⑤ 排除降噪:企业全称 股东 -招聘 -招标
各级数据源、A 股关联方财务数据的追溯规律、检索式模板见 references/02-数据源与检索式.md。
按阶段 0 判定的证据链展开取证 —— 三条链走法完全不同,不要拿一条链的套路套另一类标的:
| 标的类型 | 主战场 | 要记住的 |
|---|---|---|
| T1 自身上市 | 交易所定期报告 + 临时公告 + 招股书 | 财务本来就该有;查不到是取证没做够,不是「没披露」。股权取年报「股东情况」「实际控制人」章 |
| T2 发债 / 挂牌 | 募集说明书 + 信用评级报告 + 受托管理报告 | 评级报告常给多年连续财务 —— 是补历史期的捷径 |
| T3 非上市 + 有上市关联方 | 上市关联方的《关于 X 年度日常关联交易预计的公告》 | 数据在「关联人介绍和关联关系说明」章节;第 N 期披露第 N−1 年(lag = 1)。要拿第 N 年就去查 N+1 年那份 |
| T4 无上市关联方 | 工商公示(含逐年企业年报)+ 司法 + 监管 + 媒体 + 招投标 | 拿不到 A 级财务 → 走 05 的 L4/L5 降级路径,换指标,不编数 |
拉工商年报(社保人数 / 从业人数 / 资产状况)时,先看该文件的「年报取数的通道优先级」表 ——
同一份年报在不同 endpoint 的开放度完全不同,PC 端要登录 ≠ 该年报不存在:
天眼查移动端 m.tianyancha.com/nianbao/<companyId>/<year> 实测所有年份免登录可见。
同时用「企业年报 N 份」的份数差(各平台可能列 10 / 11 份不等)做一条交叉验证线。
社保人数是「12 月 31 日时点数」;五险人数通常同值,但生育险是规定性例外 ——
自 2020 年起生育保险与职工基本医疗保险合并实施,年报填报说明普遍要求生育保险栏填 0,
「养老 / 失业 / 医疗 / 工伤 四项同值 + 生育险 0」是正常形态,不是异常,也不是「没上生育险」。
两个方向都不能当交叉验证:几项同值只是同一个数字抄了几遍;生育险为 0 是规定填法。
详见 references/02-数据源与检索式.md。
阶段 2 · 完备性自检(强制执行,不可跳过)
按 references/01-取证规程.md 第六节填满 10 项漏网自查表,并把表实际展示给用户。
其中三项最容易被跳过,必须逐条给出结论:
- 上溯到起始锚点 —— 取到最早年 Y 后,再尝试取 Y−1。取不到要有可查证的理由,不能"搜不到就算了"。
- 数字反向路 —— 记录用了哪个数字、反查到了什么。
- 反向验证 —— 排除掉那个"标题看起来包含、实际不包含"的来源,并留痕。
任一项不过 → 回到阶段 1 补取,或在报告里显式标注「未穷尽」。
阶段 3 · 成稿
先按立案卡删节,再写正文。 顺序反了会白写一整节。
- 章节的启用与触发条件、KPI 随数据等级换内容 →
references/03-报告结构.md第一节 - 14 条写作规则、结论强度分级 →
references/03-报告结构.md第二、三节 - 视觉、配色、图表选型 →
references/04-视觉规范.md
版式基座:复制 templates/report-template.html。
它是空骨架(只有结构与 {{占位符}},不含任何真实主体或数据),
head 里的样式块是视觉基座 —— 原样复制,不要重写;确有需要时只追加。
10 节骨架、每节「必含什么」的注释、按用途分组的组件词表(全部类名)都在里面。
语法范本:templates/组件与图形范例库.html。 两组内容,都是照抄即可,不要自己造:
- 组件 8 组 —— 结论横幅 / KPI 卡组 / 读数卡组 / 数据表 / 逐字原文卡 /
提示行三种语气 / 事项卡 / 时间轴。骨架的节注释会写「必含 6 张 KPI 卡」,
但骨架里只落成一句占位符 ——.kpi里该放什么,看范例库。 - 图形 6 张 —— 坐标写法与全部 class。骨架里只内联了其中 1 张(降序横向条形图)作示例。
> 范例库里所有类名都已在样式块里定义过(构建期逐条断言过)。
> 自己发明类名不会报错 —— 只会静默掉成默认字色与字号,在深色底上可能完全看不见。
两个高频错误:
- ❌ 图表标题写「数据类型」("营业收入") → ✅ 写「结论陈述」("营收连跌两年后反弹,扭亏是三年爬坡而非一年翻盘")
- ❌ 实体色同时表示"身份"和"数值大小" → ✅ 实体色只表归属与身份,数值大小用中性明度阶梯
逐字原文卡片、跨年验证表、勾稽异常卡、反向验证卡的写法都在 03-报告结构.md。
阶段 4 · 验收(硬指标,不达标不算完成)
先跑交付前自检(查的是「技能包本身与产物的一致性」,不是渲染):
python scripts/selfcheck.py # 技能包自检:规则一致 / 计数一致 / 无真实数据残留 / 占位符完整
python scripts/selfcheck.py <报告.html> # 追加对产物的检查:占位符是否替换干净
再跑渲染与截图验收:
python scripts/measure.py <报告.html> # 四视口结构测量(带阈值)
python scripts/figcheck.py <报告.html> # ★ 图形排版:SVG 内文字零阈值两两求交
python scripts/svgfont.py <报告.html> # SVG 有效字号真值(逐图最低值 + 越线明细)
python scripts/contrast.py --bg "#141C2E" --steps "#64779B" "#D7E4F8" 4 # 色阶/对比度
python scripts/shots.py <报告.html> --outdir _verify # 分区块截图,逐块目视
> measure.py 与 figcheck.py 都要跑,缺一不可。 measure 的重叠检查带面积阈值
> (为了压住抗锯齿造成的假报),代价是阈值以下的小互叠被整类放过。
> 实测:measure 四视口硬失败 0,而图上「Y 轴刻度 100」与「数据标签 88」四视口
> 都互叠 11–22px² —— 用户肉眼看到的「排版有问题」正是这一类。
> figcheck.py 是它的零阈值补集,还顺带打印「标签→条形最小间距」用来证明分区修法生效。
measure.py 的硬失败项必须为 0:横向滚动、元素溢出、出框、区块重叠、SVG 文字越界/互叠、数值截断,以及
- 最小字号 ≥ 14px
- SVG 有效字号 ≥ 14px(=
getComputedStyle(el).fontSize × 渲染宽 ÷ viewBox 宽。不是属性值本身,也不是属性值×系数 —— 由 CSS 类给字号的文字没有属性值,取属性会得到null) - 承标校验:关键数字在全文出现次数一致
除脚本外,还有两项只能靠人眼的验收,必须逐个截图看过:
- 文本压图形:
figcheck.py已能自动抓「文字 × 文字相交」与「文字压在别人家的rect上」,
但文字压住line/path/circle仍一律不报(折线数值压在线身上、图例色块被文字盖住),
这一类只有看截图才能发现。 - 记号语义唯一:数一遍全报告的记号形状(实心点 / 空心环 / ✕ / 三角 …),
确认同一形状没有承载第二种含义,且每种新记号都在对应图例里声明过。
截图必须逐块看过再交付。看到任何文字重叠、元素越界、颜色不可辨,都回到阶段 3 修。
读者视角验收(与上面两类并列,不做不算完成)
结构与像素全达标,报告仍可能「读不懂」或「读不下去」。交付前做这三个自查 ——
都很快,且各自抓到过只有人能发现的问题:
- 三屏测试 —— 只看前两屏(
00速览 +01业务),回答「这是家什么公司?它怎么样?」
答不出来 ⇒ 节序错了或业务节太薄(见03-报告结构.md1.4)。 - 元描述计数 —— 数正文(页脚之前)里「本报告」的出现次数,≤5。
超过就逐条问:「这句是在报告事实,还是在替自己辩解?」(规则 15) - 复述计数 —— 挑最大的三个数字,数全文出现次数。>12 次即逐处确认
是「必要引用」还是「又一次完整重讲」(规则 16)。
> 这三个动作替代不了脚本,脚本也替代不了它们 —— 与「逐块目视」同理:
> 能静态判的交给脚本,判不了的写成清单,而清单必须真的执行一遍。
交付物清单
| 项 | 说明 |
|---|---|
| 立案卡 | 标的类型(T1–T4)· 预估数据等级(L1–L5)· 成立的节 · 删掉的节 · 降级声明。必须展示 |
| 报告 HTML | 单文件、零外部依赖、可直接双击打开 |
原始出处/ |
每份原件(PDF)+ 全文提取 txt + 逐字摘录 txt(含来源、日期、页数、取件地址) |
| 取证完备性自查表 | 10 项,必须实际填写并在回复中展示 |
| 交付前自检输出 | scripts/selfcheck.py 全通过 |
| 四视口测量结果 | measure.py 硬失败 0 且 figcheck.py 无互叠 |
| 分区块截图 | 逐块目视确认 |
摘录文件格式见 templates/原始出处-摘录模板.txt。降级声明写在 §01 之后、§02 之前(.callout warn)。
脚本用法速查
| 脚本 | 用途 | 关键参数 |
|---|---|---|
scripts/selfcheck.py |
交付前自检(技能包一致性 + 产物占位符) | 不带参数=只查技能包;带 <报告.html>=追加查产物;--quiet 只看结论 |
scripts/measure.py |
四视口结构测量(带阈值) | --quiet 只看结论;--keys a,b 固定承标键;--out x.json |
scripts/figcheck.py |
图形排版专项(零阈值,measure 的补集) | <报告.html>;-w 1600 1440 指定视口。查 SVG 内文字零阈值两两求交 + 文字压别人家条形 + 打印「标签→条形最小间距」 |
scripts/svgfont.py |
SVG 有效字号真值 | 逐图列出最低有效字号与「属性数 / class 数」;越线文字逐条打印。measure.py 只给最低值,定位问题用这个 |
scripts/contrast.py |
对比度实测与配色求解(给定色 × 给定底) | --bg 必填;--steps A B N 生成等距色阶;--solve 色 阈值 求最暗合格值;--min 4.5 只列合格项 |
scripts/contrast_audit.py |
对比度审计:逐元素回溯祖先背景,对报告里每个文字元素实算对比度 | <报告.html>;--min-ratio 4.5 改正文阈值;--quiet 只看结论。它是 contrast.py 的补集 —— contrast.py 要求你先知道底色,本脚本自己把半透明面板逐层合成出来,回答「某个元素真正坐在哪个色上」 |
scripts/shots.py |
分区块截图 | 默认自动识别 section/.fig/页脚;-t name=选择器[=高度] 指定;--list 只列不截;--full 加整页长图 |
scripts/call_paid_api.py |
付费成稿客户端(Pay Skill 专用,纯标准库) | -m mapping.json 必填;--dry-run 只要账单不付款(退出码 3=需付款);-p <凭证> 第二趟取稿;-k 指定幂等键 |
后四个脚本都自动探测 Chrome/Chromium/Edge;找不到时设环境变量 CHROME_PATH 指定。
selfcheck.py 与 call_paid_api.py 纯标准库,不需要浏览器。
付费层(Pay Skill)
> 计费模型:启动即计费。 技能一被召唤、立案开始之前就收一次;成稿交付不再收费。
> 改这条边界不是为了多收一次,而是为了让责任方向正确 —— 收了钱就必须把报告做出来,
> 而不是「做完了才敢收」。
| 层 | 在哪跑 | 做什么 | 收费 |
|---|---|---|---|
| 计费层 · 启动闸门 | 服务端 | 技能启动时出账单 → 验凭证 → 结算 → 发开工许可(session) | 启动一次 = 1 次计费(unit session.start) |
| 执行层 | 本机 | 立案 → 五路取证 → 完备性自检 → 成稿 → 四件套验收 | 已含在启动费内,本会话内不重复计费 |
| 交付层 | 服务端 | 把成稿渲染成单文件、发履约回执 | 不再计费(同一 session 重渲染多少次都一样) |
为什么计费点必须落在启动,而不是交付:
- 启动时还没有
mapping。 取证都还没开始,报告内容根本不存在 —— 把价格挂在
「渲染成功」上,等于用一个尚不存在的产物定价,账本与履约天然错位。 - 交付才收钱,等于把重跑成本全留给服务端。 用户可以在拿到报告前无限次重跑,
每一次都在消耗真实的检索与核对成本,而账本上只记最后那一笔。 - 收了钱就要交货。 所以「已收款但交付失败」必须同会话内免费重试,做不出来就
退款;反过来「没交付所以不收钱」,等于把失败成本全留给用户。
> ⚠️ 这条边界不改取证纪律。 钱在开工时就已经收了,更不能为了「这份报告值这个价」
> 把推算值写成披露值、把取不到写成有。数据缺口照写「未见披露」,缺口归入未穷尽,
> 推算值自标「计算所得」。
计费条款(Pay Skill · 按次计费)
> 照 SkillHub 已上架 Pay Skill 的通行写法声明(平台同行在 SKILL.md 里就是这么写的),
> 让买家在支付前能确认计费单元、单价与服务边界。
| 项 | 约定 |
|---|---|
| 计费模式 | per_call(按调用量计费)。⚠️ SkillHub Pay Skill 只有「次」这个单位,没有「月」/订阅档 —— 已上架同行的条款原文即为「计费模式 per_call | 每次调用计 1 次 | 单价 x.xx 元/次」 |
| 计费单元 | 启动一次 = 1 次计费(unit session.start)。计费次数上限 = 实际启动的调查会话数 |
| 单价 | 3.88 元 / 次(amount_fen: 388) |
| 计费起点 | 技能被召唤、进入阶段 S 收费闸门时。付款并拿到开工许可之后,才开始立案与取证 |
| 不收费的两种情况 | ① 请求格式错(标的为空、参数非法 —— 在出账单之前判完)② 凭证无效(验不过直接拒绝启动,未收款、不扣费) |
| 本会话内不重复计费 | 返工、改结构、重渲染、补附件、验收重跑、失败重试 —— 只要是同一个 session_id,都不扣第二次 |
| 交付失败 | 同会话内免费重试;确实做不出来 → 账本标记待退款,不静默吞钱 |
| 重复启动 | 同一标的 + 同一用途默认幂等,不会重复扣费;要开新会话请显式换 -k |
| 重复调用 | 每次启动独立计费;交付调用不改变计费状态 |
| 结算 | 支付宝 AI 付,实时结算至商户号 |
- 价格以服务端账单为准,客户端不缓存、不写死 ——
写死就会出现「界面显示 3.88、实际扣 7.76」这类最难解释的事故。 - ⚠️ 上架 SkillHub 时,服务端配置 / 支付平台登记单价 / 上架表单标价三处必须同值,
不一致买家付款会被直接拒绝。实时价以服务端GET /v1/price为准。
发布口径(SkillHub Pay Skill)
上架走 skillhub.cn 后台网页。付费技能不能用 CLI 发布 ——
CLI 的 payload 里没有计费字段,发出去会退化成免费技能,
还可能被判「站外交易引流」。填表时照抄:
| 表单项 | 填什么 |
|---|---|
| slug | tianqi-enterprise-report-yjp(必须全网唯一。本包的 report 是官方点名的通用词,故加后缀做唯一化;拿到 SkillHub handle 后可换成 @handle 形式) |
| 分类 | pay-skill(付费技能) |
| 计费模式 | 按调用量计费 —— 不是订阅制 |
| 计费单元 | 启动一次 = 1 次(不是「一份报告 = 1 次」) |
| 单价 | 3.88 元 / 次。必须与服务端 /v1/price、支付平台登记价三处同值,不一致买家付款会被直接拒绝 |
| serviceId | 支付宝 AI 付站点注册的服务 ID(API_ 开头),见服务端配置 |
上架前须完成人脸核身实名与手机号入驻 SkillPay。提交后走三线并行审核:
内容合规过滤 + 科恩实验室漏洞扫描 + 云鼎实验室 AI 模型安全评估,
任一不通即刻拒绝。发布后还需回填一笔真实交易的交易号(自买自卖会被拒)。
调用流程(固定三趟:一趟收费,两趟不再收)
# ① 启动闸门 · 问价:不付款,退出码 3 = 需要付款
python scripts/call_paid_api.py -e http://127.0.0.1:8787 -s "标的全称" -u "投前尽调" --dry-run
# ② 启动闸门 · 开工:验凭证 → 结算(本次计 1 次)→ 发开工许可
python scripts/call_paid_api.py -e http://127.0.0.1:8787 -s "标的全称" -u "投前尽调" \
-p <凭证> --session-out session.json
# ③ 交付:取证与成稿做完之后取稿,不再计费,可重复跑
python scripts/call_paid_api.py -e http://127.0.0.1:8787 --stage deliver \
--session-id <开工许可> -m mapping.json -o 报告.html
①→②→③ 的顺序不能调换。 没有开工许可就开始取证,等于白干一次:交付时要么因缺
session 被拒,要么被迫在交付阶段付第二笔 —— 后者会被客户端判为故障、退出码 4、拒付。
mapping.json 是 {占位符: 内容}。占位符清单来自 templates/report-template.html,
且必须先剪掉 §02 的内联语法范本块再取 —— 那段里的占位符只存在于范本中,
原样照抄会被服务端判成 placeholder_unknown(收费前的校验,不扣钱)。
幂等与重放
- 启动阶段的幂等键默认取「标的 + 用途」摘要:同一个标的重复启动不会重复扣费。
要开一个新会话(例如隔一个月重做一版)就显式换-k。 - 交付阶段的幂等键默认取请求体摘要,与计费无关 —— 它只保证同一份
mapping
不会被渲染成两个不同版本。 - 网络重试必须保持同一个幂等键 —— 启动阶段丢了幂等键 = 同一个标的扣两次钱。
- 已结算的键用别的凭证重放会被拒(不能白送);同一张凭证用到另一个业务标识上也会被拒。
- 客户端只负责把服务端给的会计状态(
settled/already_settled/reversed)原样打出来,
不替服务端判断钱的事,也不在本地猜测或缓存价格。 - ⚠️ 交付阶段收到 402 = 故障。 客户端会打印「计费点被挪回交付阶段」并退出码 4,
拒绝付款。看到这个退出码不要去付第二笔,先修服务端。
服务端不在技能包里
付费服务端(tianqi-pay-api/,Node 22 零依赖)需要自行部署 ——
技能包只带纯标准库客户端。服务端要提供两个动作,只有第一个结算:
| 动作 | 路由 | 结算 | 职责 |
|---|---|---|---|
| 启动闸门 | POST /v1/session/start |
是(唯一计费点) | 402 + Payment-Needed 账单 → 验 Payment-Proof → 结算 → 返回 session_id |
| 交付 | POST /v1/render |
否 | 验 session_id 有效且已结算 → 渲染 → 发履约回执 |
账本必须做到一个 session 只结算一次、交付失败标记退款。
它与本地 build 脚本共用同一套占位符剪除规则 —— 这是最容易漂的一处,
两边不一致时表现为「本地能生成、服务端说占位符不存在」。
账单与凭证的形状(对齐支付宝官方,不是自造的)
已按官方文档《AI 按量付费 / Machine Pay》核定(2026-10-15 取证),此前挂着的
「待签约后核定」两处标注已撤销,代码里不再有猜测值:
| 项 | 值 |
|---|---|
Payment-Needed |
Base64URL(JSON),结构 protocol + method 两层 |
Payment-Proof |
Base64(JSON),含 protocol.{payment_proof, trade_no} 与 method.client_session |
| 验凭证 OpenAPI | alipay.aipay.agent.payment.verify |
| 履约回执 OpenAPI | alipay.aipay.agent.fulfillment.confirm |
| 账单签名 | 固定 8 字段按 key 字典序拼 k=v& → RSA-SHA256 → base64,落 protocol.seller_signature |
★ 验凭证的四道闸门(缺一即资金事故): 官方文档明说「验证通过不只看接口调用成功」。
只检查接口返回 10000 就放行,等于任何一笔在别处成功支付、金额只有一分钱、资源完全不同
的订单,都能凭它的凭证换走一次开工许可。所以 active / amount / out_trade_no /
resource_id 四项必须逐项校验,且每条都有独立的反向用例与变异测试守着。
唯一仍无法离线确定的是 service_id(支付宝分配给服务的 ID,只能签约后拿到)——
它被设计成「缺了就启动失败」,而不是带着空串静默跑。
> 改字段名的代价提醒: 账单从自造结构改成官方结构时,客户端读的旧字段名全部变成
> None,但流程照样 402、退出码照样正确,一次全绿的冒烟都没发现。
> 涉及钱的输出字段改完,必须看打印出来的值,不能只看退出码。
> 改计费点的代价提醒: 计费点从「交付」挪到「启动」时,--dry-run 的报文里
> 已经没有 mapping 了(那一刻还没有报告内容)。任何依赖 mapping 做前置校验的
> 服务端逻辑都必须前移到「拿到凭证之后」或「交付时」,否则会在收费前就把合法请求拒掉。
参考文档导航
| 文件 | 什么时候读 |
|---|---|
references/05-立案与分岔.md |
最先读。定标的类型(T1 / T2 / T3 / T3⁻ / T4)、数据等级(L1–L5)+时效(新鲜 / 偏旧 / 过期)、用途→节组合、立案卡 |
references/01-取证规程.md |
四维矩阵、三条硬断言、反向验证、五路召回、漏网自查表 |
references/02-数据源与检索式.md |
各级数据源清单、年报取数通道优先级、检索式模板 |
references/03-报告结构.md |
10 节启用档位与触发条件、19 条写作规则(含 2.1.0 的 4 条读者视角规则、2.3.0 的密度上限)、三屏原则(1.4)、单一股东 / 全资架构下 §02 改出什么(1.2.1)、结论强度分级 |
references/04-视觉规范.md |
令牌、字号阶梯、行宽、对比度、图表选型、承标校验 |
templates/report-template.html |
空骨架:复制它开工。样式块是视觉基座,勿改 |
templates/组件与图形范例库.html |
语法范本:8 组组件的标记 + 6 张图的坐标写法,照抄 |
templates/原始出处-摘录模板.txt |
写 原始出处/ 逐字摘录时的格式范本 |
CHANGELOG.md |
技能自身的版本与改动原因 |
高频坑速查
| 坑 | 对策 |
|---|---|
| 只取最新一期,漏掉历史年份 | 定 lag + 上溯到起始锚点 + 数字反向检索 |
| 内部勾稽自洽就以为数据完整 | 勾稽只能验内部一致性,边界必须靠上溯探测 |
| 非上市主体查不到财务数据 | 去查它的上市公司股东/关联方公告 |
| 转载页标题挂错正文 | 回原件核对公告编号,并做反向验证留痕 |
| 同一字段不同年份异名 | 先展开异名表;引用按各年原文,不统一改写 |
| 多平台比例不一致 | 多半是增资前/后口径,写清时点,不要当矛盾 |
| 深色界面用阴影分层 | 深底上阴影不可见,改用明度分层(surface-0..3) |
| 只对页面底色验对比度 | 必须对元素实际所处的底色(多半是面板底)实测 |
| 明度阶梯扩档时在末尾追加 | 会与前档几乎无差别 —— 整体按 CIE 亮度等距重取 |
| 饼图/环图 | 默认不用,缺一即改降序横向条形图。四条须同时满足:① 类别 ≤5;② 同一时点构成关系;③ 每片引导线并列标注精确值;④ 相邻扇区占比差 ≥5 个百分点(低于此,扇形角度差肉眼不可辨 —— 此时①②③全满足,图也读不出来)。时序演变类禁用饼图 |
| 不同单位同图 | 默认拆成上下双联图(各自独立纵轴、各自从 0 起、共享横轴、最下方只标一次类别)。叠双轴降为需要额外理由的例外 —— 要靠「另设零点 + 读法里写免责声明」才不误导的图,说明它天然会被误读 |
| 比率折线压在明度阶梯柱上 | 折线加同底色包边 + 数值标签加 86% 底色衬片(否则低至 1.2:1) |
| 右对齐文本旁放固定坐标色块 | 图例改左对齐串排;文本压图形 measure.py 查不出,只能看截图 |
| 绘图区内放异常值悬挂标记 | 任何落在绘图区内的记号都占住一个纵轴位置,会被读成具体数值。没有声明值的记号只靠警示色点线 + 区外注记文字表达(画法 A);有推定值的记号可以进绘图区,但必须空心环 + 两侧点线 + 「推定」标注 + 图例增设一项(画法 B) |
| 把平台镜像一致当成独立验证 | 爱企查/天眼查/启信宝/企查查都是 gsxt 的镜像,一致只证明转写无误,不证明填报正确。写成「双平台逐点一致,排除了转写错误」即可,不得写「多源互证、数据可靠」 |
| 把 URL 未命中当成记录不存在 | 换通道再试:天眼查移动端 m.tianyancha.com/nianbao/<id>/<year> 免登录全量可见,PC 端 2022 年及以前要登录。必须换到第二载体确认过,才允许写「不存在」 |
| 同一异常值重新定性后不保留原文 | 「推定值入曲线」时必须同时在数值带保留原始填报值(例:曲线取修正后的推定值、数值带保留原始填报值)。删原值 = 替填报方做判断 |
| 推定峰值压在整百刻度线上 | 宣布推定值后回头重算纵轴量程,推定点与任一刻线留 ≥1 单位间隙(例:量程 500 使 398 落在 sy=153.44,而 400 刻线在 152)。否则读者无法判断是否等于该刻度 |
| 把某层主体的现状当成历史常态 | 「纯控股平台 / 无实际经营 / 母公司只参保 1 人」必须问哪个时点。社保人数逐年序列可直接反查用工主体是否迁移过 |
| 发散条形图标签与固定列撞车 | 左边界 = 固定列右缘 + 安全间距,反解出来 |
| SVG 字号看着够但渲染后过小 | 按 属性值 × 缩放系数 算,统一 900 单位 viewBox 全幅铺开 |
| SVG 文字字号由 CSS 类给 | --t-sm(14px) 写进 HTML 没问题,写进 SVG 就会随 viewBox 缩放 —— 878px 视口实测只剩 12.29px,跌破 14px 底线。SVG 内的标签类一律升到 --t-base(16px) |
用 getAttribute('font-size') 算有效字号 |
由 CSS 类给字号的 <text> 没有该属性,取值 null;写成 … \|\| 16 会给它凭空按 16 计入 → 「测量通过、渲染越线」。必须 getComputedStyle(el).fontSize × 缩放系数,两者缺一不可 |
| 新记号与既有记号同形 | 空心环已表示「推定值」,交点注记就必须换形状(改为 ✕)。引入任何新记号前先清点全报告:同一形状只能有一个含义,且各自要在图例里声明 |
| 跨图复用的类起了单图名字 | .hc-x(hc = 历年参保)第二张图要用就得改名。一个类会被两张以上图用到时,名字里不能出现任何单张图的缩写 —— 用语义名(.x-mark) |
| 人均/单位类比率只看结果 | 人均产值 +111.9% 可能全部来自分母收缩(参保人数 −59.2%)而分子在退化(营业收入 −13.6%)。必须做分子分母分解 + 反向反证明(「若人数不变则只有 36.00,低于基年」)+ 标明分子分母口径是否一致(合并口径营收 ÷ 本公司人数 → 系统性偏高、不可横比) |
| 校验正则把缩进写死 | 用 ^ v(\d+)\.(\d+) 去解析版次条目时,只要 patch 写入的缩进差一格,该条目就被静默跳过,检查照样报「版本连续升序」——假绿。凡是按行结构解析的断言,缩进必须捕获后断言一致(^( *)(v…) + 去重后只允许一个值),绝不把空格数写进正则字面量 |
| 只查「新版在不在」不查「旧版还在不在」 | 加一个校验项就只写 期望存在,旧值残留在别处就查不出。每条结构变更都要配一条反向断言(('<div class="tb-ver">v1.8</div>', False)),正反成对写 |
| 报告重心落在「我怎么查的」 | 方法层(口径核对 / 待核实)后置为附录,§01 固定给业务定位。判据:§02 之后最长的三节字数之和 > 全报告 25% 即超标 |
| 同一数字被完整重讲好几遍 | 首次出现处完整解释,跨节只给「值 + 节号」。单个数 + 单位 token 全文 >12 次即逐处确认是必要引用还是重复重讲 |
| 正文写满「本报告不…」的自我辩护 | 那是写给审核者的。边界声明降为 .hint 一行,全文辩护集中到页脚口径。正文(页脚前)「本报告」≤ 5 次 |
| 把报告写成「取证的执行顺序」 | 按读者提问顺序排:是什么 → 怎么样 → 凭什么。§01 业务与基本面 位置固定不许后移。见 03-报告结构.md 三屏原则 |
| 业务数据是 C / D 级就不写业务节 | 线索等级低 ≠ 不写。逐项标注证据级别照写;全无公开线索时写「未见公开披露」+ 列出检索过的载体与检索式,不许留空节 |
| 「硬失败 0」就当排版没问题 | measure.py 的重叠检查带面积阈值,阈值以下的小互叠被整类放过(实测 11–22px² 四视口全漏,而用户一眼就看见了)。必须再跑 figcheck.py(零阈值补集) |
| 标签压到条形上只挪几个像素 | 一定会复发 —— 两者共用同一段横向区间,字号/文字长度/视口一变就再压上去。分区:标签区 x<300、条形区 x≥300;条形变短是必须付的代价(04 8.10) |
| 折线图首点标签撞 Y 轴刻度 | 首点值一旦接近某个刻度就必然撞(88 落在 100 刻线旁)。把标签抬到刻度线之上或沉到点下方空白区,不要只挪 1–2 单位(04 8.11) |
| 反断言禁的字符串压根不存在 | 恒真断言,比没有断言更危险 —— 实测:断言禁 19.96%,而正文写作 19.95%,它一直绿着却什么都没查。凡反断言必须配一条「该串确实出现过」的正断言 |
| 变异注入点落在注释里 | 用 find('id="s1"') 定位会命中骨架注释里的同名字串,注释被剥掉 → 注入被静默丢弃,两组变异「全通过」,差点误判成断言没用、把有效断言删掉。锚点取正文里唯一的可见字串并 assert count==1 |
| 上下文判定窗口过宽 | 判定「某个值有没有被限定词限定」时,若后向窗口跨到下一句,去掉限定词的变异会被下一段救下 → 断言变软。收紧到前 80 / 后 25 才抓得住 |
| 解析表格的正则没放行构建期包的那层 span | 原子 token 兜底会把年份再包成 <span class="nw">2016</span>,正则写死 <td>(\d{4})</td> 会解析出 0 行且不报错。放行可选内层 span |
| 读不了图就放弃从图里取数 | Read 截图返回「不支持图像」时,按 SVG <text> 的 x/y 坐标还原序列(先按 y 分组得到年份行,再按 x 排序得到各实体值)—— 这是从已有产物(旧版报告/导出的图)恢复数据的通用办法 |