非创新型公司年度经营复盘(ROIC × g 骨架)
通用方法论,不绑定具体公司。
> 交付约定(第一重要):一次复盘只交付一份 HTML 文件——六章合一、全部表格内联。
> 不输出 PDF、不单独交付 CSV 与分章子表 HTML。详细规则见第五章末「交付约定」。
> 大白话版见同目录 README.md —— 开头第一段就是「这个 skill 是干嘛的」,回忆用途时先看它。
用大白话说它是干嘛的
给一家已经跑通商业模式的公司做年度体检,核心只回答两个问题:
这门生意本身好不好? → ROIC(投入的钱一年挣回百分之几)
这门生意还在不在长? → g(收入/利润还在不在增长)
然后把这两个数字拆开:ROIC 拆成"卖得贵"还是"转得快",g 拆成"哪个业务在长""哪个区域在长""为什么那一年突然加速"。最后用一张 T 字表看钱从哪来、投到哪去——因为今天的投入决定明天的 g 和 ROIC。
零、先判断:这家公司适不适用(第一步,不要跳过)
本 skill 的适用前提是:投入与产出在时间上基本匹配。
适用(非创新型)
- 制造业、CXO、器械、消费、化工、工程机械、零部件
- 商业模式已定型,收入来自已上市产品/服务
- 当期 capex 在 1–3 年内转化为收入
不适用(创新型)
- 创新药 / 早期 biotech:研发投入在当下,收入在 5–10 年后,且成败取决于临床结果。当期 ROIC 必然极低甚至为负,g 也不可预测 → ROIC × g 框架失效
- 尚未有收入的平台型公司、纯管线公司
- 判断标准:若「当期投入 → 当期收入」的链条断了,就不要用这个骨架
> 遇到创新药类公司,明确告诉用户"这套框架不适用",并说明原因。不要硬套。
边界情形(可用但要声明)
| 情形 | 处理 |
|---|---|
| 公司有创新药子公司但主体是制造业 | 复盘主体,创新药部分单列说明,不计入 ROIC 主口径 |
| 转型期(老业务下滑、新业务未起量) | 照做,但第五章 T 字表要重点看新业务投入占比 |
| 重资产 + 长周期(如造船、军工) | 可用,但 g 的拆解要用在手订单而非当年收入 |
一、六章结构总览
| 章 | 内容 | 自动化程度 | 工具 |
|---|---|---|---|
| 一 | 现象描述:年度段十二项指标逐年 + 季度段最近 6 个季度(单季) | 全自动(员工两列需另取,见下) | 本 skill scripts/annual_review.py + scripts/quarterly.py |
| 二 | 拆解收入增长的原因 | 数据半自动 + 叙述人工(年报 + 卖方点评 + 电话会纪要) | 模板 + 检索 |
| 三 | 特殊项目历年梳理 | 人工(有则写,无则不写;年报 + 卖方点评 + 电话会纪要) | 模板 |
| 四 | ROIC 的变化及原因(两层拆解) | 全自动 | roic-decomp skill |
| 五 | 资金的来源与运用(T 字表) | 全自动 | sources-uses skill |
| 六 | 概括性总结 | 人工 | 模板 |
执行顺序建议:一 → 四 → 五 → 二 → 三 → 六。
先拿三个自动章节把骨架立起来,再回头补需要判断的部分。
二、第一章:现象描述(十二项指标)
十二项指标(用户定稿,不要增删)
| # | 指标 | 口径 | 数据源 |
|---|---|---|---|
| 1 | 收入(亿元) | 年度营业总收入 | 东财单季利润表按年加总(不足 4 季的年走年度利润表,见下「早期年份怎么取」) |
| 2 | 收入增长(%) | 逐年同比,首年为 — | 自算 |
| 3 | 扣非归母(亿元) | 扣除非经常性损益后归母净利润 | 财务摘要年度披露值(兜底:单季 DEDUCT_PARENT_NETPROFIT 加总) |
| 4 | 扣非归母增长(%) | 逐年同比,首年为 — | 自算 |
| 5 | ROIC(剔商誉) | 主口径,见下 | 自算 |
| 6 | ROIC(含商誉) | 对照组,商誉留在 IC | 自算 |
| 7 | 总资产(亿元) | 年末时点值 | 东财年度资产负债表 |
| 8 | 总资产增长(%) | 逐年同比,首年为 — | 自算 |
| 9 | 员工总数(人) | 期末在职员工数 | 见下「员工数怎么取」 |
| 10 | 员工总数增长(%) | 逐年同比,首年为 — | 自算 |
| 11 | 当年股价涨跌幅(%) | 后复权年末收盘 ÷ 上年末收盘 − 1 | 东财后复权日线 stock_zh_a_hist(adjust="hfq") |
| 12 | 当年重大事件 | 极简,20 字内 | 人工 / 检索草稿 |
> 第 11 列「当年股价涨跌幅」的用处与口径(2026-09-22 用户要求新增):
> 放在数字区末尾、重大事件之前(重大事件要保持最右宽列,CSS 靠 td:last-child 定位)。
> 它回答一个第一章原本答不了的问题:"经营的变化,市场当时认没认?"——
> ROIC 与 g 讲的是基本面,这一列讲的是市场对基本面的定价,两者背离的地方就是第二章和第六章要解释的地方。
> 英科医疗实例:2020 年收入 +564%、扣非 +4045%、股价 +1405%(基本面与股价同向爆表);
> 2021 年收入还在涨(+17.4%)股价却 −46.5%,2022 年收入 −59% 股价 −52.9%
> ——2021 年就是"基本面顶点已过、股价提前反应"的时点,只看财务表会晚一年。
>
> 口径三条硬约束:
> 1. 必须后复权。不复权会把送转/大额分红的除权跳空算成暴跌(英科 2020 年有送转,
> 不复权序列直接失真)。脚本取 adjust="hfq"。
> 2. 上市首年基数取上市首日收盘,单元格加 * 标记。
> 不用发行价:发行价→首日收盘那段是打新收益,不属于二级市场持有者能拿到的收益。
> 3. 末年若尚未结束,按年初至今(YTD)计,单元格加 YTD 标记。
> 上市前年份填「—」——这是公司属性造成的天然空缺(不是取数失败),脚注自动说明,绝不手填。
> 4. 季度段该列填「—」(年度口径,与单季无对应关系),但列要保留,理由同员工两列。
> 第 8 列「总资产增长」的用处(2026-09-17 用户要求新增):
> 与收入增长并列看,一眼判断「资产扩张快于/慢于收入」——
> 资产增速持续高于收入增速 = 单位资产产出在下降,这正是 ROIC 要下滑的前兆;
> 反之则是效率改善。它是第四章「经营性长期资本周转率」在第一章就能看到的前哨,
> 不用等拆解表出来。实例(普洛药业):2021–2024 总资产增长 21.5%/32.9%/6.2%/−0.3%
> 而收入增长 13.5%/17.9%/8.8%/4.8% —— 资产跑在收入前面,ROIC 从 27.90% 一路降到 20.08%。
早期年份怎么取:季度接口 + 年度利润表兜底(2026-09-22 新增)
annual_review.py 的利润表主通道是季度接口按年加总(stock_profit_sheet_by_quarterly_em),
但它对每家公司的回溯深度不一样。实测:
| 公司 | 季度接口最早 | 年度接口最早 |
|---|---|---|
| 药明康德 SH603259 | 2017Q1 | 2017 |
| 英科医疗 SZ300677 | 2016Q3 | 2013 |
→ 若只用季度接口,做「英科 2013 年以来」会踩两个坑:
1. 2013–2015 直接缺年份(老脚本此时会打印 !! 缺少年份 并整个退出,一个字都不输出);
2. 2016 有 Q3+Q4 两条记录,加总出来是"半年"——11.83 亿 被算成 6.4 亿,
静默错误,表格里看不出任何异常,比缺年份更危险。
规则:某年季度记录 < 4 条时,该年整行改取年度利润表
(stock_profit_sheet_by_yearly_em,等于年报披露值,更权威)。满 4 条的年一律不变,
以免动到既有公司(药明 / 凯莱英 / 康龙)的既有基准。
配套改动(同一批):缺年份不再致命退出,改为分层降级——
利润表缺 → 该年收入/扣非/ROIC 留空;首年缺上一年末资产负债表 → 该年 ROIC 留空(脚注自动说明)。
这样"2013 年起"这种首年缺期初 IC 的情形也能出表(英科 2013 年 ROIC 即留空)。
> ⚠️ 实现坑(见易错点 29):年度接口有 200+ 列含文本列,直接 df / 1e8 会
> TypeError: unsupported operand type(s) for /: 'str' and 'float',
> 而且这个异常会被 tqdm 进度条那一行吞掉(进度条用 \r 不回车),
> 日志里看起来像"兜底压根没触发"。必须只挑出要用的字段再转数值。
季度段:年度序列后面接最近 6 个季度(2026-09-17 用户要求,方案 A)
为什么要有:年报有滞后。现在 2026-09,最新年报是 2025 年报,2026 已经过了大半年
却是空白。在年度序列后面接最近 6 个季度(单季),把最新情况看得更细。
表格形态(两段拼接,视觉分隔):
════════ 年度段(颗粒度=年)════════
… 2023年报 2024年报 2025年报 ← 到最近一个完整财年
════════ 季度段(颗粒度=季)════════
2025Q1 Q2 Q3 Q4 2026Q1 2026Q2 ← 最近 6 个单季,同比对比去年同季
2026H1(累计) ← 末行:官方披露累计值
⚠️ 两段是"上下拼接"而非"替代"——年度段正常排到最近完整财年(2025),
季度段在后面另起一段(不是把 2025 年报替换成 4 个季度)。
实测数据(普洛药业,2026-09-17):
| 季度 | 收入(亿) | 收入同比 | 扣非(亿) | 扣非同比 | 总资产(亿) | 总资产同比 |
|---|---|---|---|---|---|---|
| 2025Q1 | 27.30 | −14.6% | 2.06 | −14.0% | 127.1 | −5.7% |
| 2025Q2 | 27.14 | −16.0% | 3.15 | −13.3% | 124.8 | −7.6% |
| 2025Q3 | 23.19 | −18.9% | 1.27 | −44.7% | 121.0 | −9.3% |
| 2025Q4 | 20.20 | −26.1% | 1.22 | −20.3% | 123.2 | −3.2% |
| 2026Q1 | 24.47 | −10.4% | 2.43 | +18.1% | 122.5 | −3.6% |
| 2026Q2 | 23.76 | −12.5% | 2.52 | −19.9% | 121.0 | −3.1% |
| 2026H1(累计) | 48.23 | −11.4% | 4.95 | −4.9% | — | — |
> 这张表立刻能看出:收入同比跌幅在收窄(−26% → −10%~−12%),
> 2026Q1 扣非同比转正 +18.1%,但 Q2 又回落 —— 是年度表(只到 2025 的 −21.9%)
> 完全看不到的信息。这就是季度段的价值。
字段口径(已实测确认,勿改):
| 项 | 口径 | 来源 |
|---|---|---|
| 收入 / 扣非 | 单季值(不是累计) | stock_profit_sheet_by_quarterly_em |
| 收入/扣非同比 | 对比去年同季单季,不是环比 | 自算 |
| 总资产 | 季末时点值 | stock_balance_sheet_by_report_em |
| 总资产同比 | 对比去年同季末(保持与年度同比可比) | 自算 |
| 末行累计值 | 官方报告期披露值 | stock_profit_sheet_by_report_em |
| ROIC 剔商誉 / 含商誉 | 不列示(—) | — |
| 员工总数 / 员工增长 | 不列示(—),列保留 | 半年报/季报不披露 |
| 当年股价涨跌幅 | 不列示(—),列保留 | 年度口径(年末÷上年末),与单季无对应关系 |
三个为什么(这些是设计决策,别动):
- 为什么单季不用累计 —— 累计只有半年/全年两个颗粒度,"6 个季度"会退化成 2 个半年,
失去"颗粒度更高"的意义。单季的代价是季节性噪声大(制造业 Q1 通常最淡),
所以同比必须对比去年同季,让季节性自动抵消。 - 为什么末行另给官方累计 —— 单季加总 ≠ 半年报披露值(小数差、追溯调整)。
披露值优先于推导值(这是本 skill 的长期规则,见易错点 10)。
实测普洛 2026H1:单季加总 48.23 = 官方累计 48.23,本例一致,但接口保证不了永远一致。 - 为什么季度段不放 ROIC —— ROIC 是年度概念:
NOPAT = EBIT_op × (1−t)的 EBIT_op
是年累计量,单季要年化(×4),而制造业季节性极强,年化后严重失真;
且 IC 均值的期初/期末口径在季度上没共识。放进去会破坏与年度口径的可比性,
而"可比"正是 ROIC 存在的意义。这一章必须保持年度。 - 为什么员工两列保留但填「—」 —— 用户 2026-09-17 明确要求"保留列但填 —",
而不是删列。理由:保持与年度段列结构一致,读者不会以为"漏了",
一眼就知道"这列季度没有"而不是"取数失败"。
(2026-09-22 新增的股价涨跌幅列同理:年度口径,季度段填「—」但列保留。)
员工数怎么取(2026-09-16 实测,2026-09-17 已自动化)
akshare / 东财 / 同花顺的财务接口都没有员工人数,别在这些接口里翻。
> ✅ 已封装,不用手写:scripts/employee.py 自动抓 F10 并填进第 8、9 列,
> annual_review.py 默认就会调用(除非加 --no-emp)。下面留作原理与手工兜底参考。
可用通道(按优先级):
- 大智慧 F10「员工构成」表(推荐,有完整年度序列)
http://webf10.gw.com.cn/{SH|SZ}/B7/{SH|SZ}{6位代码}_B7.html 例:http://webf10.gw.com.cn/SZ/B7/SZ002821_B7.html
页面里两张表:
-◆员工构成◆:报告日期 / 公布日期 / 职工总数 / 按专业构成 / 按学历构成(逐年)
⚠️ 不是<table>,是<ul>/<dl>/<dd>结构;这是用 table 正则抓不到的原因
-◆员工薪酬◆:员工人数 / 员工薪酬 / 人均薪酬 / 人均创收 / 人均创利 / 营业收入 / 净利润
⚠️ 这张表的<dl>里没有<dt>,报告日期是第一个<dd>
覆盖范围:只回溯最近 10 期(实测普洛药业给到 2016–2025)。
更早年份需走下面第 2 条通道,补完存成 emp_{6位代码}.csv(列:年份,员工数)
放到输出目录,脚本会自动读取。
校验:营业收入 ÷ 职工总数 应等于该页「人均创收」。对得上就说明职工总数是期末口径,可以放心用。
- 公司年报「董事、监事和员工情况」章节的"在职员工的数量"(最权威,但一年一年翻,慢)
批量取数可复用cninfo-periodic-reportsskill 下 PDF 后抽取 - 券商深度研报里常直接写"员工总数由 X 人增长至 Y 人"
必写的一段解读:员工数比收入更能暴露周期位置——
扩张期连续大幅招人、过剩期停止招人甚至净减员、回暖期重新招人。
再补一句人均创收(收入÷期末员工)的走势,能看出"效率改善"是真还是被大订单抬的。
> 实例(普洛药业 SZ000739,F10 实测):
> 2017–2019 连续减员 6,176→5,719(整合期)→ 2020–2023 连续增员 5,868→7,208
> 收入同步高增长(扩张期)→ 2024–2025 重新减员至 6,787(收入下滑后的调整)。
> 这条曲线比收入更早提示了 2024 年之后的收缩。
为什么第 5、6 列要并列
两个 ROIC 的差距本身就是信息:
- 差距小 → 公司靠内生增长,商誉不影响判断
- 差距大 → 大量并购形成的商誉在拖累真实回报,剔商誉的才是主口径,含商誉的用来说明"市场看到的账面回报被高估/低估了多少"
第 8 列「重大事件」的硬约束
- 20 字内,只写事实("某某产品获批""某某法案出台""某某大客户丢失")
- 不写评价,不写股价反应(那些是第二章和总结的事)
- 没有事件 CSV 就留空,绝不编造
- 带
[草稿]前缀的=检索得来、尚未核实,写进第二/六章前必须逐条核实,
核实完把 CSV 里的类型从草稿改成定稿再重跑一次(前缀会消失)
跑法
# 默认:年度段 + 季度段(最近 6 个单季),一步全出
python scripts/annual_review.py SH603259 2014 2025
python scripts/annual_review.py SZ300759 2020 2025 --name 康龙化成 --out <输出目录>
# 季度段行数可调(默认 6)
python scripts/annual_review.py SZ000739 2002 2025 --quarters 8
# 只要年度段,不抓季度段(老口径 / 离线时可跳过联网)
python scripts/annual_review.py SZ000739 2002 2025 --no-quarters
输出目录里的输入 / 缓存 CSV 命名约定(脚本会按这些名字自动发现,一律用 6 位数字代码,不带 SH/SZ 前缀):
| 文件名 | 作用 | 谁来写 |
|---|---|---|
events_{6位代码}.csv |
逐年重大事件(列:年份,事件,类型;类型=草稿 会加 [草稿] 前缀) |
人工准备 |
emp_{6位代码}.csv |
补齐 F10 覆盖不到的早期员工数(列:年份,员工数) |
人工准备,或 --emp-csv 指定 |
price_{6位代码}.csv |
股价涨跌幅缓存(列:年份,涨跌幅%,标记) |
脚本自动写;接口不通时自动读回兜底 |
> ⚠️ 写成 events_SZ300677.csv 会静默找不到(脚本内部 CODE 只保留数字)。
> ⚠️ 事件文本里含英文逗号(如 7,292.948)时必须加引号或去掉千分位,
> 否则会被 CSV 解析截断——2026-09-22 实踩。
完整六章的报告怎么拼:第一章表格、第四/五章表格由脚本各自产出独立 HTML,
拼成一份总报告时不要直接内联它们的 <style>——三份 CSS 都有 table/th,td/thead th
这类通用选择器,会互相覆盖(例如 ROIC 表的深蓝表头会被现象表样式顶掉)。
正确做法:把每份 CSS 的每个选择器加上 .chN 前缀做作用域化(丢弃 body/html/h1 规则),
再把各表格包进 <div class="chN">。另需在 @media print 里加
-webkit-print-color-adjust:exact(保底色)、thead{display:table-header-group}(长表跨页重复表头),
并显式重设 .tacc{display:flex!important}(否则 A4 打印会把 T 字表压成上下布局)。
参数说明:
| 参数 | 默认 | 作用 |
|---|---|---|
--quarters N |
6 |
季度段行数(最近 N 个单季) |
--no-quarters |
关 | 跳过季度段,只输出年度表 |
--events <路径> |
自动找 | 显式指定事件 CSV |
--out <目录> |
CWD | 输出目录 |
--name <公司名> |
自动 | 覆盖输出文件名里的公司名 |
季度段单独自测(只抓季度、不出表):
python scripts/quarterly.py SZ000739 6
事件 CSV 格式。文件名必须是 events_{6位数字代码}.csv(603259,不带 SH/SZ 前缀),
放在输出目录、CWD 或脚本目录都能自动找到;也可以用 --events <路径> 显式指定。
三列:年份 / 事件 / 类型,类型填 定稿 或 草稿:
年份,事件,类型
2024,生物安全法案立法进程,定稿
2021,某基地投产,草稿
2022,欧洲区产能翻倍,定稿
为什么要「草稿」这一档(用户 2026-09-16 拍板加的):
做复盘时经常先让助手去检索一遍事件,检索出来的东西没有逐条核实过,
直接和已核实的定稿事件混在一张表里,事后分不清哪条能信。
所以规定:
- 检索得来的 → 一律标
草稿,脚本会在事件前自动加[草稿]前缀 - 人工核实过的 → 标
定稿,不加前缀 - 表里两种前缀并存时,写报告前先把
[草稿]的逐条核实,核实完把类型改成定稿
只有 年份,事件 两列的旧 CSV 也能读(自动当定稿处理),不用改。
产出:{公司}_{代码}_年度现象表_{起}_{止}.html / .csv
ROIC 口径(与 roic-calc / roic-decomp / sources-uses 完全一致,不得改)
ROIC = NOPAT ÷ IC均值
NOPAT = EBIT_op × (1 − t)
EBIT_op = 营业利润 + 财务费用净额 − 投资收益 − 公允价值变动损益
− 资产处置收益 − 其他收益 − 营业外收支净额
(汇兑中性化:整体调整量 = +财务费用净额)
t = 所得税费用 ÷ 利润总额
IC = 有息负债 + 股东权益合计 − 超额现金 − ω 组资产
有息负债 = 短期借款 + 一年内到期非流动负债 + 长期借款 + 应付债券 + 租赁负债
超额现金 = 货币资金 + 交易性金融资产 − 营业收入 × 5% (为负取 0)
ω 组 = 长期股权投资 + 其他非流动金融资产 + 其他权益工具投资(全额剔除)
剔商誉口径:再减商誉
IC均值 = (期末 + 期初) ÷ 2
参数:θ=5%、汇兑中性化、减值不加回、实际税率。
三、第二章:拆解收入增长的原因(本章最费工,也最有价值)
2.1 增长阶段:五次换挡(时间维度)
把区间切成 3–5 个阶段,每个阶段一句话概括增长特征:
阶段 | 年份 | 收入 CAGR | 特征 | 驱动
切分依据(优先级从高到低):
1. 增速的数量级变化(如 20% → 5% → 30%)
2. 重大产能投放 / 并购 / 政策拐点
3. 行业周期位置
2.2 业务拆解(空间维度)
核心难点有两个:披露口径会变 + 披露频率只有半年。
时间颗粒度:到「年份 + 最新半年报」为止(2026-09-17 用户要求)
> 用户原话:"2.2 业务拆解 / 2.3 区域拆解,颗粒度到最新半年报。"
为什么不能像第一章那样接到季度段——分部数据根本没有季度披露:
| 报告期 | 是否披露分部(主营业务构成) |
|---|---|
| 一季报 | ✗ 不披露 |
| 半年报 | ✓ 披露(H1 单期值) |
| 三季报 | ✗ 不披露 |
| 年报 | ✓ 披露(全年值) |
stock_zygc_em 实测(普洛药业 SZ000739,2026-09-17):24 个报告期,
日期全部是 06-30 或 12-31,无一例 03-31 / 09-30。
→ 这是信息披露制度决定的,不是取数没取够,不要试图去找季度的分部数据。
所以 2.2 / 2.3 的时间轴 = 年度列 + 末尾追加一列最新半年报:
业务 2021年报 2022年报 2023年报 2024年报 2025年报 2026H1 ← 追加
原料药 … … … … … …(单期)
CDMO … … … … … …
制剂 … … … … … …
末尾半年报列的三条硬约束:
- 标清是"H1 单期值"而不是"年化" —— 表头写
2026H1,
脚注写明"半年报口径,为上半年单期收入,未年化" - 不与年度列直接比大小 —— H1 必然比年报小,读者会误读成"下滑"。
正确读法是同一列内比占比(结构变化)或与去年 H1 比增速(如有上年半年报) - 占比重算,不沿用上年 —— 半年报的分母是该半年报的总收入,
重新算一遍各业务占比;直接抄年报占比会全错
口径问题:披露口径会变,必须做多口径拼接
这是本章最容易出错也最容易被忽略的地方。规则:
- 先把公司历年用过的披露口径列全(招股书口径 / 上市后口径 / 分部调整 / 终止经营重述)
- 每个口径单独成表,标注适用年份,不要强行把不同口径的数字拼成一条连续曲线
- 若必须拼接成连续序列:自定义一个主口径(如 CRO/CDMO 两段法),并说明"本文主口径",其他口径作为对照
输出格式(每个口径一张表):
业务 | Y1 收入 | Y1 占比 | Y1 增长 | Y2 ... | 最新 H1 收入 | H1 占比
红线:
- 口径变化的年份必须明确标注(如"2024 起 ATU + DDSU 划入终止经营")
- 占比之和必须 = 100%(分母是总收入,不是分部收入合计,有差额要说明)
- 分部收入加总 ≠ 总收入时(内部抵消、其他业务),单列一行"其他/抵消",不要硬凑
取数:ak.stock_zygc_em(symbol="SZ000739") → 列含
报告日期 / 分类类型 / 主营构成 / 主营收入 / 收入比例 / 主营成本 / 毛利率。
注意 分类类型 一列同一报告期有多个分类维度(按产品 / 按地区 / 按行业),
2.2 用「按产品」、2.3 用「按地区」,选错类型会把两个维度混成一张表。
2.3 区域拆解
同 2.2 的时间颗粒度(年度列 + 末尾追加最新半年报)与口径规则,按区域
(中国 / 美国 / 欧洲 / 其他,或按客户所在地)。
> 注意:2.3 的分部数据与 2.2 来自同一个接口的同一张表,靠 分类类型 区分
> (如「按地区」)。不要另找接口。
重点关注单一区域占比的长期趋势——如某区域从 53% 升到 77%,这既是增长来源也是风险集中点,要在总结里点出来。
最新半年报那一列尤其要看:境外占比的拐点往往先在半年报里露头(如关税、汇率、地缘影响)。
2.4 逐年增长原因说明(年层 + 季层,2026-09-17 用户要求)
> 用户原话:"2.4 逐年增长原因 / 2.5 前瞻指标,听你的。"(即采纳建议:改「年层 + 季层」)
> 与第一章方案 A 同一思路:年层看趋势,季层看最新。
结构:先按年写,末尾接一段最新季度
【年层】每年 1–3 句,必须有出处 ← 主粮,覆盖全区间
2018 … 2019 … … 2025(最近完整财年为止)
【季层】最近 2–4 个单季,各 1 句 ← 补充,看最新拐点
2025Q4 … 2026Q1 … 2026Q2
为什么加季层:年报次年 3–5 月才出,写复盘时最新的半年经营已经"过期"。
最新几个单季的同比变化,往往能提前暴露年报里还没体现的拐点
(某大客户掉单、某产品降价、某基地停产检修)。
年层告诉你"过去五年怎么走的",季层告诉你"现在正在往哪走"。
季层的写法(三句话以内):
- 只写与年层趋势不一致的信号(年层说在复苏、Q2 却掉头 → 必须写并在总结里提示风险)
- 与年层一致时一句话带过或省略,不要重复叙述
- 出处同样要求:季报原文 + 季报后的卖方点评(季报点评比年报点评少但更快)
注意季层的"原因"比年层更难拿到:季报的「经营情况讨论」只有几段,
管理层口头归因要靠季报电话会纪要(如公司开)。拿不到就只写数据变化,
不要硬编原因——这一条比年层更要守。
年层:每一年写 1–3 句,必须有出处
四个来源,按优先级:
| 顺序 | 来源 | 说明 |
|---|---|---|
| 1 | 公司年报「经营情况讨论与分析」 | 最权威,等于公司口径 |
| 2 | 每年年报后的卖方点评(券商事件点评 / 业绩点评) | ⭐ 2026-09-17 用户指定新增。年报出来 1–2 周内密集发布,专门解释当年为什么增/减,往往把「量/价/结构」拆得比年报更清楚 |
| 3 | 电话会纪要 / 业绩说明会纪要(如有) | ⭐ 2026-09-17 用户指定新增。管理层口头归因,常给出年报里没有的定性解释("某大客户订单前置""某产品降价换量") |
| 4 | 券商深度研报的归因段落 | 汇总性强,但时点滞后,适合做交叉验证 |
取数路径:
- 卖方点评:文件名多为
{日期}_{券商}_事件点评_{代码}{公司}_{分析师}_{标题}.pdf,
本地存在外部研究/目录;检索时按「年报披露日 + 30 天」为窗口找
(季层同理,窗口换成季报披露日 + 15 天,季报点评发得更快) - 电话会纪要:本 skill 不自己去抓,交给
jiyao-archiveskill 已归档的纪要
(公司目录下跟踪纪要/子文件夹),或用local-company-query按公司名检索本地库 - 两者都必须标注来源(哪家券商 / 哪场会 / 日期),不要凭印象写
写法要求:
- 写「这一年为什么是这个增速」,不是复述数字
- 归属不清时写「年报归因于 X,卖方认为是 Y」(如实呈现分歧,不要强行统一)
- 找不到出处的年份宁可留白,不要用常识补
数据来源标注(不要凭印象写)。
2.5 收入增长的前瞻指标(年层 + 季层,2026-09-17 用户要求)
这是本章最有投资价值的部分——回答"未来的 g 会怎样"。
结构:年层找"长期可比",季层找"最新方向"
【年层】至少 5 年(能到全区间更好)的长期序列 ← 判断"这个指标值不值得信"
【季层】最近 2–4 个单季的读数(如有披露) ← 判断"现在指向哪"
两层各管一件事:
| 层 | 看什么 | 为什么 |
|---|---|---|
| 年层 | 指标的长期序列(≥5 年、口径一致) | 单点无意义;只有长期序列才能看出"这个指标和收入到底有没有领先关系" |
| 季层 | 该指标的最新读数 | 前瞻指标的价值在"快"。年层告诉你它历史上管用,季层告诉你它此刻怎么指 |
季层的可得性因指标而异 —— 不要硬凑:
| 前瞻指标 | 是否按季披露 | 季层怎么办 |
|---|---|---|
| 在手订单 | 多为半年 / 年(如凯莱英) | 取最近一期披露值,标注"截至 2026H1" |
| 在建工程 / capex | 季报就有(资产负债表科目) | 直接取最近 2–4 个单季 |
| 产能利用率 | 多为半年 / 年 | 同在手订单 |
| 客户数 / 项目数 | 多数只在年报 | 季层留空,不编 |
→ 规则:按公司实际披露频率处理。有季度的走季度,没有的就标"截至 XXXXH1",绝不年化换算。
第一步:先找「每年都常规披露的」前瞻指标(2026-09-17 用户要求)
> 用户原话:"2.5 收入前瞻指标,新增两个线索来源——每年年报后卖方的点评和电话会纪要(如有)里,
> 在这里找每年都常规披露的。"
关键约束:要的是「长期可比」的指标,不是某一年提一次的概念。
判断方法(满足全部三条才算):
- 连续出现 —— 至少在 3 个以上年份的卖方点评 / 纪要里都出现
- 口径一致 —— 不是今年说"在手订单"、明年说"意向订单"
- 领先收入 —— 领先 1–4 个季度,且不本身是收入的组成部分
两个来源怎么用:
| 来源 | 找什么 | 为什么好用 |
|---|---|---|
| 每年年报后的卖方点评 | 研报里被反复引用的运营指标(在手订单、产能、项目数、客户数) | ⭐ 卖方为了做模型会逐年维护同一套指标,天然是长期序列;研报表头/摘要常直接给历史值 |
| 电话会纪要(如有) | 管理层被反复问到的那个数(分析师年年问,说明市场认它是前瞻指标) | ⭐ "年年被问"本身就是筛选器——能被连年追问的指标才是有共识的前瞻指标 |
> 实操技巧:把一家公司 3–5 年的卖方点评/纪要并排读,
> 看哪个指标年年都出现。出现频率最高的那个,就是这个行业的前瞻指标。
> 只在某一年出现的("某新签订单""某次中标")不进本章。
按公司类型选指标(用户可覆盖):
| 类型 | 前瞻指标 |
|---|---|
| CXO / 订单驱动 | 在手订单、管线项目数、客户数 |
| 制造业 / 重资产 | 在建工程、capex 强度、产能利用率 |
| 消费 | 渠道数、SKU 数、经销商库存 |
| 器械 | 装机量、招标中标数 |
判断标准:该指标必须领先收入 1–4 个季度,且不本身就是收入的组成部分。
四、第三章:特殊项目历年梳理
原则:有就写,没有就不写,绝不编造。
判断"值不值得写"的三条标准:
1. 公司连续披露(不是一次性的)
2. 对经营判断极端重要(影响未来收入或 ROIC)
3. 无法从常规财务科目直接读出
常见候选:
| 项目 | 典型例子 | 时间颗粒度 |
|---|---|---|
| 在手订单 | CXO、工程机械、造船 | 按公司实际披露频率(多为半年/年) |
| 客户结构(集中度 / 大客户占比 / 客户数) | 所有 To-B 公司 | 多为年报 |
| 产能建设(物量口径 + 固定资产 / capex) | 制造业、CDMO、化工 | capex 走单季(见下) |
| 管线/项目数 | CXO 小分子 D&M 管线、临床批件 | 多为年报 |
| 新业务曲线 | 药明 TIDES(寡核苷酸+多肽) | 多为年报 |
写法:一张历年表 + 一段"这个指标告诉我们什么"。每个项目独立成小节(3.1、3.2…)。
capex 走单季(2026-09-17 用户要求)
> 用户原话:"第三章 特殊项目,听你的。"(即采纳建议:capex 走单季)
为什么 capex 特别适合单季看(与在手订单、客户数不同):
| 理由 | 说明 |
|---|---|
| 是流量,不是存量 | 在手订单是时点数(只能取到披露时点),capex 是期间流量,天然可以按季切 |
| 季度间波动大且有意义 | 单季 capex 突然放量往往对应"某基地开工 / 某产线转固前投入",是产能节奏的最早信号 |
| 领先收入 2–6 个季度 | 钱花下去到产能释放有滞后期,单季 capex 是比在建工程更前置的观察窗口 |
| 季报就披露 | 现金流量表科目,一季报 / 三季报都有,不受分部数据"只有半年报"的限制 |
取数与口径:
ak.stock_cash_flow_sheet_by_quarterly_em(symbol="SZ000739")
# capex 字段 = CONSTRUCT_LONG_ASSET(购建固定资产、无形资产和其他长期资产支付的现金)
# 该接口返回的就是单季值(与利润表单季接口同源口径)
三条硬约束:
- 单季值不加总成年内累计 —— 与第一章季度段同一原则:
需要年度数时用年报披露值,不要用四个单季加总(见易错点 10 / 23) - 与收入对照时要看"capex / 收入"比值而非绝对值 ——
单季 capex 受大额一次性投入影响极大,直接比绝对值会得出"在收缩"的错误结论;
用滚动 4 季(TTM)capex / 滚动 4 季收入才是稳态口径 - 负号与口径不要混 —— 现金流量表里 capex 是现金流出,东财字段有的带负号;
统一取绝对值再呈现,并在脚注写明"取绝对值,方向为流出"
呈现方式(与第一章季度段呼应):年度列 + 末尾追加最近 2–4 个单季,
单季列同样标"单季"、增速同比对比去年同季。
在手订单:按公司实际披露频率
在手订单不是每个季度都披露(如凯莱英多为半年/年),
按公司实际披露节奏走:有季度就列季度,只有半年就标"截至 XXXXH1",
绝不年化换算、也不要用增速反推缺失期间(与 2.5 季层同一规则)。
⚠️ 必须是多年序列,不能只给一两年(用户 2026-09-16 明确纠正)
用户原话:"在特殊项目里边涉及到的在手订单、产能建设还有客户结构,都有好几年的数据,
你在这儿只有一两年的数据,这一块信息可以不完全从年报里提取,也可以从一些研究报告里提取。"
硬要求:每个项目至少 5 年;能拿到全区间(上市以来)就拿全区间。只有一两年的要么补齐,要么别单列。
取数优先级(年报不够时往下走,不要停在"未取到"):
| 顺序 | 来源 | 说明 |
|---|---|---|
| 1 | 公司年报 / 年报摘要 / 公告原文 | 最优先,直接可引 |
| 2 | 每年年报后的卖方点评(券商事件/业绩点评) | ⭐ 2026-09-17 用户指定新增。卖方为做模型会逐年维护同一套运营指标(在手订单、产能、客户集中度),把 3–5 年的点评并排读就能拼出长序列 |
| 3 | 电话会纪要 / 业绩说明会纪要(如有) | ⭐ 2026-09-17 用户指定新增。管理层口头披露常比年报更细(如"XX 产品收入占比""某基地产能进度"),且年年被分析师追问的指标就是最该进本章的指标 |
| 4 | 公司业绩说明会 / 路演材料(官网 resources. 或 IR 页) |
常披露在手订单等运营指标,等于公司口径 |
| 5 | 券商深度研报 | 研报常把历史年份整理成表(前五大客户占比、反应釜体积、在手订单),可直接引用并注明"XX 证券研报引用公司年报" |
| 6 | 理杏仁 lixinger.com/equity/company/detail/{sh\|sz}/{代码}/{代码}/major-customers-and-suppliers |
有历史前五大客户占比与逐户金额 |
| 7 | 大智慧 / 东财 F10 | 见第一章员工数那节 |
两个新来源的取数路径:
- 卖方点评:文件名多为
{日期}_{券商}_事件点评_{代码}{公司}_{分析师}_{标题}.pdf,
本地在外部研究/目录。检索窗口 = 年报披露日后 30 天 - 电话会纪要:交给
jiyao-archiveskill 已归档的纪要(公司目录下跟踪纪要/),
或用local-company-query按公司名检索本地库;本 skill 不自己去抓
用这两个来源填长序列的正确姿势(举例,凯莱英的在手订单就是这么拼出来的):
| 年份 | 值 | 来自哪 |
|---|---|---|
| 2021 | 18.98 亿美元 | 券商研报引用年报 |
| 2022 | 11.50 亿美元 | 券商研报引用年报 |
| 2023 | 8.74 亿美元 | 公司业绩说明会材料 |
| 2024 | 10.52 亿美元 | 年报原文 |
| 2025 | 13.85 亿美元 | 年报原文 |
→ 结论:同一个指标从不同来源逐年拼是允许且必要的,但每行必须标来源,
读者才能判断哪几年是硬数据、哪几年是转引。
每行都要标注来源(新增一列),让读者能追溯。
相邻披露值之间算同比是允许的(如 2022 年 11.50 vs 2021 年 18.98 → −39.4%),
但要注明"由相邻披露值计算";用增速反推缺失年份的绝对值是不允许的。
> 若公司确实没有这类项目,本章直接省略,不要为了结构完整而凑数。这是用户明确要求的。
五、第四章:ROIC 的变化及原因(两层拆解)
直接调用 roic-decomp skill,不要在本 skill 里重算。
python $env:USERPROFILE\.workbuddy\skills/roic-decomp/scripts/roic_decomp.py SH603259 2014 ... 2025
4.1 两层拆解主表
直接用 roic-decomp 的 12 行主表输出。
4.2 仅从拆解的表里分析过去的变化趋势及原因
约束:只看表内数字,不要引入表外信息。
回答三个问题:
1. ROIC 是趋势性变化还是波动?
2. 主要是净利率驱动还是周转率驱动?(对比第 4、5 行的变动幅度)
3. 若是净利率 → 看毛利率还是费用率(第 6–9 行);若是周转率 → 看营运资本还是长期资本(第 11、12 行)
4.3 把变化的趋势及原因落实到业务层面
这一步是本章的价值所在,也是不能自动化的部分。
把 4.2 的财务结论翻译成业务语言:
- 毛利率下滑 3pct → 是哪个业务的价格战?还是原材料涨价?
- 长期资本周转率下降 → 是哪年投的产能?投产了吗?达产了吗?
- 营运资本周转率改善 → 是账期管理变严,还是客户结构变了?
恒等式 NOWC + NLOA = IC 必须内部校验(残差 ≈ 0,否则周转率是错的,详见 roic-decomp 易错点)。
但不要在报告里输出恒等式对账表 —— 用户 2026-09-16 明确要求删掉:
这是给自己查错用的中间过程,读者不需要看,占了版面还打断叙事。
残差异常时在正文用一句话说明即可(例:"早期两年残差约 IC 的 4.5%,来自科目披露不全,不影响结论")。
> 2026-09-22 更新:源头已修,拼接时不用再手工删。
> roic-decomp ≥ 1.1.0 的 roic_decomp.py 已不再向 HTML 输出这两张表 ——
> 用户 9-22 再次要求,并把「参考 · 规模量(均值,亿元)」一并点名
> (原话:「这两部分没有必要要」)。现在第四章的 HTML 只有 12 行主表 + 一条口径注释。
> 教训:上一版只在拼接说明里写"记得删",源头 roic_decomp.py 没动 →
> 此后每次生成的 HTML 都还带着这张表,用户不得不重复提。
> 凡"某块内容不要出现在交付物里",先改生成源头,再改拼接说明。
六、第五章:资金的来源与运用(T 字表)
直接调用 sources-uses skill。
python $env:USERPROFILE\.workbuddy\skills/sources-uses/scripts/t_account.py SH603259 2014 2025
区间 > 15 年时必须再跑分期:
python $env:USERPROFILE\.workbuddy\skills/sources-uses/scripts/stage_breakdown.py SH600196 1997 2025 --cuts 2007,2017
本章在整体骨架里的作用
第五章是 ROIC 与 g 之间的桥梁:
- 左(来源):钱是靠自我造血还是外部融资?
- 右(运用):钱是返还股东(分红回购,说明进入成熟期)还是变成资产(说明还在投入期)?
结论要落到:这个资本配置是在增强还是削弱未来的 ROIC 和 g。
必查
残差必须 = 0。不为 0 说明科目没归类或口径错配,先修再写结论。
⚠️ T 字表必须左右布局(用户 2026-09-16 明确要求)
用户原话:"T 字表的形式跟药明康德的差异很大,样子很丑……
要把『钱从哪里来』和『钱到哪里去』放在左右两边,而不是放在上下。"
不要输出成上下两张各自独立的 <table>(源在上、运用在下很难看)。
要用一个 CSS grid 容器,左栏=来源,右栏=运用,中间加一条竖分隔线:
<div class="tacct">
<div>
<h4>左|钱从哪来 <span>来源合计 220 亿</span></h4>
<table>……</table>
</div>
<div>
<h4>右|钱去哪了 <span>运用合计 220 亿</span></h4>
<table>……</table>
<p>残差(左 − 右)= 0 ✓ 两侧严格对账</p>
</div>
</div>
配套 CSS(可直接复制,与 药明康德经营复盘_2014_2025.html 用的是同一套):
.tacct{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--line);
border-radius:10px;overflow:hidden;margin:16px 0}
.tacct>div{padding:14px 18px;background:var(--card)}
.tacct>div:first-child{border-right:2px solid var(--hl)}
.tacct h4{margin:0 0 10px;color:var(--hl);font-size:14px;font-weight:700}
.tacct table{margin:0;font-size:12.5px;background:#fff;width:100%}
.tacct th,.tacct td{padding:5px 8px}
@media(max-width:860px){.tacct{grid-template-columns:1fr}
.tacct>div:first-child{border-right:none;border-bottom:2px solid var(--hl)}}
/* ⚠️ 打印时必须强制还原左右:A4 有效宽度约 794px < 860px,会误触上面的断点,
把 T 字表压成上下两段(2026-09-21 实际踩到)。@media print 优先级更高,可覆盖。 */
@media print{.tacct{grid-template-columns:1fr 1fr}
.tacct>div:first-child{border-right:2px solid var(--hl);border-bottom:none}}
残差那行写在右栏底部(一句小字),不要再单开一张表。
HTML 外壳与左侧导航(固化 2026-09-22,用户明确要求)
报告外观不要每次现写——外壳已抽成模板,直接复用:
templates/report_shell.html—— 完整外壳(CSS + 左侧目录容器 + scrollspy 脚本),
只留三个占位标记SHELL:TITLE/SHELL:NAV/SHELL:BODY。
取自 东富龙 300171 2010–2025 实跑稿;CSS / scrollspy JS / 正文三者与实跑稿
逐字一致(已用归一化比对验证过)。scripts/shell.py—— 渲染器:render(title, body)拼装、
build_nav(body)从正文实际锚点生成目录、check_anchors(html)反查断链。
锚点约定(改锚点就是改目录,命名不要自创):
| 元素 | id | 目录位置 | 文案来源 |
|---|---|---|---|
header.hero |
c0 |
第一条「摘要」 | 固定 |
section |
c1 … c6 |
一级 | 该 section 内第一个 h2 |
section |
ap |
一级(附录) | 同上 |
h3 |
c3-1 … |
二级 | 自身标题 |
div |
c1-1 … |
二级 | 必须带 data-nav="文案" 才进目录 |
文案覆盖:元素上写 data-nav="短文案";写 data-nav="-" 显式排除。
h2 / h3 文案自动截短到第一个 : / : / ( 之前
(「一、现象描述:十二项指标逐年」→「一、现象描述」;「2.1 增长阶段:两次换挡」→「2.1 增长阶段」),
上限 20 字。实跑验证:自动生成的目录与原稿手写目录 锚点集合一致、
文案格式一致(原稿手写 22 条,自动 21 条,差的正是挂在 div 上、需 data-nav 的 #c1-1)。
为什么目录必须自动生成:第三章「无此类项目就删章」是硬纪律,
手写目录删章时极易漏删锚点 → 点了跳到页首。build_nav() 扫正文生成,删章即消失。
div 因为没有可读标题才要手标 data-nav;h3 有标题,自动即可。
外壳里三处不能删 / 不能改:
@media(max-width:980px)里.wrap{grid-template-columns:1fr}+nav{display:none}——窄屏让位。@media print里必须同时保留.wrap{display:block}与nav{display:none}——
打印 viewport ≈700px 会误触 980 断点,不还原就只印出左半页。
(同源坑:.tacct也要在此还原成1fr 1fr,见上一节。)- scrollspy 的
scrollY+130偏移是给顶部 sticky 表头留位,改小会让高亮提前跳。
填充方式:一律 str.replace(),不要用 .format() / f-string —— CSS 满是大括号会直接
KeyError。同理,外壳的 CSS 注释里不要写带尖括号的标签名(如 main / nav 的尖括号写法),
会让「抽正文」的正则在注释里先误配一次(2026-09-22 实际踩到)。
# 用外壳合成报告(正文先落成 body.html;生成时会自动跑一次断链校验,有断链直接报错退出)
& $PY "$SK\non-innovative-annual-review\scripts\shell.py" `
--title "<公司>(<代码>)经营复盘 <起>–<止>" --body-file $TMP\body.html --out $OUT\<最终文件名>.html
# 从已有报告里抽 main 内容重排
& $PY "$SK\non-innovative-annual-review\scripts\shell.py" `
--title "<标题>" --from-report <旧报告>.html --out <新报告>.html
# 只看目录、不写文件(改章节后快速核对)
& $PY "$SK\non-innovative-annual-review\scripts\shell.py" `
--title T --body-file $TMP\body.html --out x --nav-only
交付约定(用户 2026-09-22 明确要求,此后默认执行)
> 一次复盘只交付一份 HTML 文件 —— 六章合一、全部表格内联。
> 不再输出 PDF,不再单独交付 CSV 与分章子表 HTML。
- 第一章 / 第四章 / 第五章脚本产出的 CSV 与单章 HTML 都算中间产物:
让它们落在临时目录(如%TEMP%\review_<6位代码>),不要复制到交付目录。 - 交付目录里只放一个文件:
{公司}_{6位代码}_年度经营复盘_{起}_{止}.html。 - 中间产物不要删(
verify.py与数字溯源都依赖它们),只是不交付。 - 若用户显式要求 PDF,才走下方流程;HTML 始终是主交付物。
(可选,仅用户显式要求 PDF 时)导出 PDF 必须做的两件事
@media print里显式重设.tacct的grid-template-columns:1fr 1fr(见上方 CSS),
否则 A4 打印会被max-width:860px断点压成上下布局。- 用 Chrome headless 打印,保证与 HTML 是同一套 CSS:
"C:\Program Files\Google\Chrome\Application\chrome.exe" \
--headless=new --disable-gpu --no-sandbox --hide-scrollbars --no-pdf-header-footer \
--print-to-pdf="<out>.pdf" "file:///<abs path to html>"
配套 @media print 建议:
@media print{
@page{size:A4;margin:11mm 10mm}
body{font-size:10.5px;-webkit-print-color-adjust:exact;print-color-adjust:exact}
nav{display:none} /* 隐藏锚点导航 */
thead{display:table-header-group} /* 长表跨页重复表头 */
tr,.callout,.warnbox,.tacct{break-inside:avoid}
h2,h3{break-after:avoid}
}
打印后必须回读校验:用 pymupdf 逐页取文本,确认①总页数②各章标题齐全③无 None/nan/{/} 等模板残留,
并把 T 字表所在页渲染成 PNG 目视确认仍是左右布局——只看「exit code 0」不算通过。
七、第六章:概括性总结
按模板 templates/report_skeleton.md 撰写,四个层次:
- 一句话结论:这家公司过去 N 年做了什么(用 ROIC 和 g 两个数概括)
- 三个最关键的发现(按重要性排序,每个发现要能追溯到具体章节)
- 当前的周期位置:处在扩张期/收获期/平台期/衰退期的哪一段
- 需要跟踪的 2–3 个变量(要能被后续数据证伪)
纪律:
- 总结里的每个判断都要能指回前面某一章的具体数字
- 不要引入前文没有的新事实
- 不要写"建议买入/卖出"这类投资建议(这是研究员自己的工作)
八、怎么跑(完整流程)
$env:PYTHONIOENCODING="utf-8"
$PY = "<本机 Python 解释器,见 ENV.local.md>"
$SK = "$env:USERPROFILE\.workbuddy\skills"
$TMP = "$env:TEMP\review_<6位代码>" # 中间产物落这里 —— 不交付
$OUT = "<交付目录>" # 最终只放一份 HTML —— 见「交付约定」
# 1) 先确认适用性(见第零节)
# 2) 第一章:十二项指标表(含总资产增长、员工数、当年股价涨跌幅,自动走大智慧 F10)
& $PY "$SK\non-innovative-annual-review\scripts\annual_review.py" SH603259 2014 2025 --out $TMP
# · F10 只覆盖最近 10 期;更早年份用 --emp-csv 传外部 CSV 补齐(列:年份,员工数)
# CSV 放在 $TMP 并命名 emp_{6位代码}.csv 会被自动发现,也可显式指定:
# & $PY ... annual_review.py SZ000739 2002 2025 --emp-csv $TMP\emp_000739.csv
# · 不想抓员工数时加 --no-emp(该两列留空)
# · 事件列用 --events 传 events_{6位代码}.csv(格式见 templates\events_模板.csv)
# 3) 第四章:ROIC 两层拆解
& $PY "$SK\roic-decomp\scripts\roic_decomp.py" SH603259 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 --out $TMP
# 4) 第五章:资金来源与运用
& $PY "$SK\sources-uses\scripts\t_account.py" SH603259 2014 2025 --out $TMP
# 5) 第二章、第三章、第六章:按模板人工/检索撰写
# 6) 合成与交付:把 $TMP 里三块自动表格按作用域(.ch1/.ch4/.ch5)内联进六章模板,
# 然后**只把这一份合成 HTML 复制到 $OUT**,其余文件全部留在 $TMP。
# 外观走固化外壳(见「HTML 外壳与左侧导航」):
& $PY "$SK\non-innovative-annual-review\scripts\shell.py" `
--title "<公司>(<代码>)经营复盘 <起>–<止>" --body-file $TMP\body.html --out $OUT\<最终文件名>.html
# · shell.py 会自动生成左侧目录并校验无断链;有断链会直接报错、不产出文件
# 7) 自检(改过 skill 之后一定要跑)
& $PY "$SK\non-innovative-annual-review\scripts\verify.py" --full
自检脚本
scripts/verify.py 是本 skill 的回归测试,七层:
| 层 | 查什么 | 联网 |
|---|---|---|
| A 文件结构 | 11 个必需文件在不在;README 开头有没有大白话;事件模板有没有「类型」列 | 否 |
| B 文档一致性 | SKILL.md / reference 有没有写回已修订口径(如扣非用年度披露值、十二项指标、总资产增长列、员工两列、股价涨跌幅列、年度利润表兜底) | 否 |
| C 数值回归 | 药明康德 2018–2025 十二项指标与基准逐位比对(容差 0.05) | 是 |
| D 草稿通道 | 事件 CSV 的 类型 列能不能正确加 [草稿] 前缀、无事件年份是否留空 |
是 |
| E 员工通道 | F10 抓取 + 两表交叉校验 + 人均创收对账 + --emp-csv 补齐 + --no-emp 留空 |
是 |
| F 季度通道 | 最近 6 个单季 + 官方累计行、同比非环比、ROIC/员工四列留空、--no-quarters |
是 |
| G 外壳与目录 | 外壳占位标记各 1 次、左导航 CSS 齐全、两个断点、scrollspy;目录自动生成、data-nav 生效、删章不留断链、死链能被抓到、标记重复会报错、SKILL.md 已写回这套约定 |
否 |
python scripts/verify.py # 只跑 A/B/G,秒级
python scripts/verify.py --full # 加跑 C/D/E/F,约 60–180 秒
退出码 0 = 全过。当前基线见本文件「变更记录」最新一条(静态 A/B/G 约 PASS 96)。
改了任何口径后,必须同步更新 verify.py 里的 BASELINE / EMP_BASELINE,否则自检会误报。
本机运行环境
> 机器相关配置已统一剥离到同目录的 ENV.local.md(每台机器各一份、不参与跨机同步)。
> 包含:本机 Python 解释器绝对路径、输出目录、本机特有的工具链坑。
> 本节以下内容与机器无关,两台机器一致。
- 必须用本机已装 akshare 的解释器(见
ENV.local.md)。
直接调用托管版~/.workbuddy/binaries/python/versions/3.13.12/python.exe本体没装 akshare,会 ModuleNotFoundError。 - 大输出的脚本(如
annual_review.py,100+ 行终端表格)用 Bash 工具 +> 文件 2>&1重定向,
不要用 PowerShell 管道:部分环境下 PowerShell 工具在长输出后会卡死(连Get-ChildItem都返回 1)。 - 脚本首行已内置
sys.stdout.reconfigure(encoding="utf-8", errors="replace"),
重定向到文件后中文不乱码;终端直显可能乱码,但数字与 HTML 产物正常,不影响交付。 - 接口会打 tqdm 进度条到 stderr(10–15 秒),属正常现象,不是卡死。
九、易错点
- 忘了先判断适用性 — 对创新药公司硬套 ROIC × g,得出的结论全错。
- 业务拆解时混用披露口径 — 不同年份的分部口径直接拼成一条曲线,
看起来连续其实是断的。每年口径变化必须标注。 - 占比分母搞错 — 用分部收入合计当分母,导致占比之和 ≠ 100%。
分母永远是总收入,有差额单列"其他/抵消"。 - 重大事件写成评价 — "业绩大超预期"是评价不是事实。写"H1 收入同比 +28%"。
- 第三章凑数 — 公司没有连续披露的特殊项目时,宁可空着。
- 第四章不看恒等式 —
NOWC + NLOA = IC残差不为 0 时,周转率是错的,
而只看 ROIC 对不对发现不了(ROIC 不受影响)。 - 第五章残差不查 — T 字表残差必须为 0。
- 总结里放新事实 — 第六章只能归纳前文,不能引入新信息。
- 同步盘吞编辑 — 工作区在
E:\BaiduSyncdisk时,编辑提示 Success ≠ 落盘。
每 2–3 处编辑后 grep 复验。 - 扣非归母取数必须用年度披露值,不要用单季加总(2026-09-16 用户纠正,实踩)
- 主通道 = 财务摘要的年度披露值;单季
DEDUCT_PARENT_NETPROFIT加总只作兜底。 - 理由:披露值优先于推导值。加总是推导出来的,有实打实的误差——
药明康德 2021 年度披露 40.64 亿 vs 单季加总 41.19 亿(+0.55 亿);
2022 年度 82.60 亿 vs 加总 83.45 亿(+0.85 亿)。连带增长率也错
(2021 扣非增长 70.4% vs 加总算出的 72.7%)。 - 指标名是「扣非净利润」,不是全称「扣除非经常性损益后的净利润」——
用全称匹配会命中 0 行并静默返回空表(不报错,最难发现)。 - 只取
1231结尾的列(年报列),不要取到季报列。 - 脚本双通道都会算,差异 >1% 时会在终端打
[info]提示,看到提示以年度值为准。
- 主通道 = 财务摘要的年度披露值;单季
- 财务摘要不带交易所前缀,利润表/资产负债表要带 — 混用会取不到数。
stock_financial_abstract(symbol="603259")vsstock_profit_sheet_by_quarterly_em(symbol="SH603259")。 - 第三章只给一两年数据(2026-09-16 用户纠正,凯莱英首版踩) —
在手订单 / 产能 / 客户结构这类指标公司往往已经披露多年,年报里没有就去找研报、
业绩说明会材料、理杏仁、F10,"只取到一两年"通常说明取数没做够,不是公司没披露。 - T 字表输出成上下两张独立表(2026-09-16 用户纠正:"样子很丑") —
必须左右 grid 布局,见第六章。 - 把恒等式对账表放进报告(2026-09-16 用户要求删除;2026-09-22 连规模量表一起点名) —
恒等式只做内部校验,不进正文。
已在源头修掉:roic-decomp≥ 1.1.0 的 HTML 不再输出这两张表,拼接时无需手工删。 - 以为 akshare 能取到员工数(2026-09-17) —
akshare / 东财 / 同花顺的财务接口都没有员工人数,别在里面翻。
唯一免费多年序列是大智慧 F10 B7 页面的「员工构成」表 →
已封装成scripts/employee.py,脚本自动调用,不用手写抓取。 - 用
<table>正则抓 F10 员工表(2026-09-17 实踩) —
「员工构成」表不是<table>而是<ul>/<dl>/<dd>结构。
用re.findall(r"<table.*?</table>")会静默返回空(不报错,最难发现),
于是以为"公司没披露"。同理「员工薪酬」表的<dl>里没有<dt>,
报告日期是第一个<dd>,按常规dt+dd解析同样拿不到。 - 以为 F10 能回溯到上市首年(2026-09-17) —
F10 只给最近 10 期。做"上市以来"复盘时早期年份必然为空,
这不是 bug。早期要靠年报 PDF 补,补完存成emp_{代码}.csv
放到输出目录,脚本自动读取(或--emp-csv显式指定)。
不要因为早期为空就以为整列取数失败。 - 员工数取到后不校验口径(2026-09-17) —
F10 的「员工构成」和「员工薪酬」两张表都有员工人数,必须交叉对账;
再用「人均创收 = 营业收入 ÷ 员工人数」验证一次(同页直接给了人均创收),
对得上才说明是期末口径、可以直接用。employee.py已内置这两道校验。 - 2.4 / 2.5 / 第三章只翻年报,不去找卖方点评和纪要(2026-09-17 用户要求) —
年报是"公司想让你看到的原因",卖方点评和电话会纪要是"市场追问出来的原因",
两者互补。每年年报后(3–5 月)券商都会发事件点评,本地外部研究/下
通常有 PDF(文件名格式见 2.4 节);电话会纪要(如有)在跟踪纪要/,
可用jiyao-archive/local-company-queryskill 检索。
2.4 逐年增长原因、2.5 收入前瞻指标、第三章特殊项目,三处都要过一遍这两类来源。 - 拿点评/纪要里的"观点"当"事实"写进报告(2026-09-17) —
卖方点评和纪要有大量主观判断("超预期""拐点已至""预计明年…")。
只有公司方在纪要里明确说的、或点评里引用的公司披露数字,才能作为事实写入;
分析师自己的推测要么不写,要么明确标注"某券商认为…"。
多方观点分歧时如实呈现分歧,不要挑一个当结论。无出处的宁可留白。 - 2.5 前瞻指标抓到"只在某一年提过"的一次性数据(2026-09-17) —
用户明确要求"在这里找每年都常规披露的"。判断标准三条:
① 连续出现 ≥3 年;② 口径一致(同样定义、同样单位);③ 领先收入 1–4 个季度。
三条不全满足的,只能作为 2.4 的单年佐证,不能进 2.5 的前瞻指标表。
实操技巧:把历年电话会纪要连着看,年年被分析师追问的那个指标,
通常就是市场认可的前瞻指标——"年年被问"本身就是筛选器。 - 季度段用"环比"而不是"同比"(2026-09-17 用户要求,最容易犯) —
季度段每期的增长率必须对比去年同季(2026Q1 vs 2025Q1),
不是对比上一季(2026Q1 vs 2025Q4)。制造业(普洛就是)有极强的季节性,
用环比会把"淡季"读成"下滑",方向都反了。
实测量级差:普洛 2026Q1 收入同比 −10.4%,环比却是 +21.1%——
同一个数,一个说在跌、一个说在涨,写进报告就是事故。
quarterly.py已按同比实现,不要手动改成环比。 - 把"单季加总"当成官方累计值(2026-09-17) —
季度段末行给的是官方披露的累计值(半年报/年报口径),
不是前面几行单季数字加出来的。两者会有差异(只在定期报告体现的年末调整、
重述、合并范围变化),披露值优先于推导值(见易错点 10,同一原则)。
quarterly.py两个通道都会算,差异 >1% 时打[info]提示,以披露值为准。
口径确认:stock_profit_sheet_by_quarterly_em返回的就是单季值,
stock_profit_sheet_by_report_em才是累计值——两个接口别用混。
(普洛实测对账:2026Q1 单季 24.47 + Q2 单季 23.76 = 48.23 = 半年报累计 48.23 ✓) - 季度段里放 ROIC 和员工数(2026-09-17 用户拍板) —
这两个都是年度概念,放季度段必然失真:- ROIC:分母 IC 是期初期末均值,单季年化会把季节性波动放大成假趋势,
且与年度段的年度口径不可比。 - 员工数:半年报/季报根本不披露员工人数,只有年报有。
正确做法:列保留、值填「—」(用户原话:"季度员工两列要保留列但填—")。
保留列是为了让季度段和年度段视觉上对齐、列宽一致,
读者一眼知道"这两项季度没有",而不是以为漏了。
- ROIC:分母 IC 是期初期末均值,单季年化会把季节性波动放大成假趋势,
- 季度同比「凭感觉填」而不是用脚本算(2026-09-20 康龙化成实踩,最危险) —
写 3.1 的 capex 单季表时,把 2025Q1 同比臆断为「+233%(低基数)」、
2026Q2 臆断为「+74.8%」;实测值分别是 +11.5% 和 +31.9%。
根源是想当然认为"2024Q1 基数低",而实际 2024Q1 = 4.99 亿并不低
(真正的谷底是 2024Q2 = 4.52 亿)。方向没错但量级错了一倍,写进报告就是事故。
→ 规则:任何同比/环比/累计数字,必须用脚本对原始序列算出来再填,
不得根据"看起来在低位"推断。 填完再用python -c复算一遍逐位比对。 - 起始年早于公司有财报数据的年份(2026-09-20 康龙化成) —
康龙 A 股 2019 年上市,2017 年及以前东财无财报数据。
从 2015 或 2017 起跑会直接报「缺少年份(ROIC 需期初值)」,无产出。
→ 规则:跑之前先确认区间首年有数据。 经验起点:
创业板次新股取上市前一年(康龙 2019 上市 → 从 2018 起),
港股/A 股双重上市的公司以先上市地为准。若报缺年份,逐年往后推一年即可,
不必怀疑脚本。 - 第五章筹资净额算不平(2026-09-20 康龙化成) —
吸收投资 + 取得借款 − 偿还债务 − 分红付息只是主项,
康龙实测 65+156−107−26 = 88 ≠ 筹资净额 56,
差额是「支付其他与筹资活动有关的现金」31 亿(回购/租赁付款/收购少数股权)。
→ 规则:呈现筹资明细时必须把「其他筹资」一行也列出来,否则读者算不平。
同理,B 项分项加总可能因取整比 Δ总资产差 1–2 亿,要在表下加一句取整说明,
不要默默让它对不上。 -
导出 PDF 时 T 字表被压成上下布局(2026-09-21 东富龙实踩) —
第五章 CSS 里有@media(max-width:860px){.tacct{grid-template-columns:1fr}}
是为窄屏准备的,但 A4 有效宽度约 794px < 860px,打印时同样命中该断点,
于是本该左右并排的 T 字表在 PDF 里变成上下两段,正好违背用户 2026-09-16 的明确要求。
→ 规则:@media print里必须显式重设.tacct{grid-template-columns:1fr 1fr}
(打印媒体查询优先级高于宽度断点,可直接覆盖)。
且打印完必须把 T 字表页渲染成图目视确认——只看 Chrome 退出码 0 会漏掉这个坑,
因为布局压扁不报错。参见第五章「导出 PDF 时必须做的两件事」。 -
以为季度利润表能回溯到公司最早年份(2026-09-22 英科医疗实踩) —
季度接口(stock_profit_sheet_by_quarterly_em)的回溯深度因公司而异:
药明康德到 2017Q1,而英科医疗只到 2016Q3。做「2013 年以来」时会踩两个坑:
① 2013–2015 直接缺年份(老版本此时打印!! 缺少年份并整个退出,无任何产物);
② 2016 只到 Q3+Q4,加总出来是半年 ——11.83 亿被算成6.4 亿。
这个错误不报错、表格也看不出异常,比缺年份更危险。
→ 规则:某年季度记录 < 4 条时整行改取年度利润表(stock_profit_sheet_by_yearly_em)。
跑新公司前先print(df["Y"].value_counts())看一下各年季度条数,别假设都是 4 条。 - 年度利润表接口直接除以 1e8(2026-09-22 实踩) —
年度接口有 200+ 列,含SECUCODE/ 公司名 / 币种 / 报告类型等文本列,
df.groupby("Y").last() / E会TypeError: unsupported operand type(s) for /: 'str' and 'float'。
且df.apply(pd.to_numeric)对StringDtype列不一定降级为数值。
→ 正确做法:只挑出真正要用的字段(use = sorted({pc[k] for k in pc if pc[k] in dy.columns}))
再apply(pd.to_numeric, errors="coerce"),然后才除。
⚠️ 更坑的是这个异常会被 tqdm 进度条吞掉:进度条用\r不换行,
print出来的报错被拼到进度条那一行,用grep -v "it/s]"过滤日志时会连同报错一起被滤掉,
表现成"兜底代码压根没写进去"。排查日志要先把\r换成\n(tr '\r' '\n' < log)。 - 股价列用不复权价(2026-09-22 新增列时明确) —
不复权序列会把送转 / 大额分红的除权跳空算成暴跌(英科医疗 2020 年有送转,
不复权看会凭空多出一根巨阴线)。必须adjust="hfq"(后复权)。
另外上市前年份必须填「—」而不是 0,并脚注说明"公司未上市"——
填 0 会被读成"当年平盘"。 - 同一个文件并行提交多处编辑(2026-09-22 实踩,工具层) —
一次性对同一文件发起 3 处 Edit,工具都回Successfully edited,
但只有 1 处真正落盘,另外 2 处被并发读-改-写互相覆盖。
→ 规则:同一文件的多次修改必须串行,每改完 1–2 处就grep复验关键词是否落盘,
不要凭"Success"就认为改上了。
真实受害案例(2026-09-22 再踩):加第 11 列「当年股价涨跌幅」时,
终端输出那一处改上了、HTML 渲染那一处被覆盖丢掉了,
表现为 HTML 里该列只有光秃秃的数字(<td>79.0</td>)——
没有%、没有上市首年*、也没有涨跌着色,而终端打印却完全正常。
靠"看终端输出没问题"是发现不了的,必须抽查生成的 HTML 单元格原文。 - 东财 K 线接口会被代理间歇性挡住,整列股价变空(2026-09-22 实踩) —
同一台机器、同一天:17:03 能取到,18:00 起连续ProxyError/ConnectionError,
重试 4–8 次全失败。此时第 11 列整列留空,报告直接缺一列。
→ 已加股价缓存兜底:成功时把结果写到<输出目录>/price_{6位数字代码}.csv,
接口不通时自动读回并打印[info] 改用缓存(已结束年份的值不会变,缓存就是正确口径),
缓存里缺的年份仍留空、不手填。
→ 附带教训:缓存文件名必须用 6 位数字代码(price_300677.csv),
不是带交易所前缀的price_SZ300677.csv——脚本里CODE = "".join(ch for ch in SYM if ch.isdigit()),
用错前缀会静默找不到缓存。events_{代码}.csv同理。 - 新增的输入/缓存 CSV 会被
verify.py误当成结果 CSV(2026-09-22 实踩) —
verify.py用csvs[0]取结果 CSV,靠前缀白名单排除输入文件。
新加price_*.csv后 C / D 两层立刻报KeyError: '收入(亿元)'——
因为 ASCII 的price_排在中文文件名之前,被优先取走了。
→ 规则:每新增一类会写进输出目录的 CSV,必须同步把它的前缀加进
verify.py的排除列表(现有三处:run_raw/check_numbers/check_draft,
统一排除events_/emp_/price_)。 -
交付物越了界:多给了 PDF / CSV / 分章 HTML(2026-09-22 用户明确纠正) —
用户原话:「以后只生成一个 html 文件就行了,别的不用了」。
当时交付目录里堆了 6 个文件:报告 HTML、同源 PDF、现象表 CSV、现象表 HTML、
ROIC 拆解 HTML、T 字表 HTML——用户只想看到一份。
→ 规则(见第五章末「交付约定」):三个脚本的产物一律落临时目录,
交付目录只放一个{公司}_{6位代码}_年度经营复盘_{起}_{止}.html;
PDF 仅在用户显式要求时才做;中间产物不要删——
verify.py的 C/D/E 层与正文数字溯源都依赖它们。
→ 判断口径:"交付"指用户会打开的文件;"中间产物"是流程内部要用的。
不要把中间产物顺手复制进交付目录,"反正不占地方"不是理由。 -
外壳的 CSS 注释里写了带尖括号的标签名(2026-09-22 固化外壳时实踩) —
report_shell.html的说明注释里写了<main>/<nav>的尖括号写法,
结果「从完整报告里抽正文」用的<main[^>]*>(.*?)</main>正则
在注释里先误配了一次,抽出来的是注释尾巴 + 正文,比对时一度误判为「正文不一致」。
→ 规则:HTML 模板的注释一律不写带尖括号的标签名(写 main / nav / title 即可)。
同类隐患还有<script>/<style>字面量;真需要举例就用&lt;main&gt;。
这条不只影响正则——它也让「打开源码看一眼」变得不可读。 -
用
.format()/ f-string 填充 HTML 模板(2026-09-22 固化外壳时明确) —
外壳 CSS 里满是大括号(:root{}、@media print{}、.wrap{display:grid;…}),
.format()会把{box-sizing:border-box}当占位符 → 直接KeyError。
→ 规则:占位符用<!-- ==== SHELL:XXX ==== -->这种注释式标记,
填充一律str.replace();shell.py的render()还会先断言
「每个标记在全文中恰好出现 1 次」—— 出现 2 次(例如把标记写进了说明注释里)
会在替换前就报错,而不是产出一份坏文件。 -
只在拼接说明里写"记得删某块",却没改生成源头(2026-09-22 实踩) —
恒等式对账表 2026-09-16 就记下"不要输出",但只记在拼接环节,
上游roic_decomp.py一直在生成 → 之后每次跑出来的第四章 HTML 都还带着它,
用户 9-22 又说了一遍(这次连「参考·规模量」表一起点名)。
→ 规则:"某块内容不要出现在交付物里",先改生成源头,再改拼接说明。
只在拼接说明里记 = 每次都要靠人记得手删 = 迟早忘。
报告里的数字要回头自查(2026-09-16 教训)
凯莱英首版正文写"2021–2023 年累计 capex 98 亿",实际那三年是 50.53 亿
(16.60 + 21.51 + 12.42),98 亿疑似从别的公司/别的区间的数字串过来的。
教训:正文里每一个"累计/合计"数字,生成后都要用分项加总复核一遍;
校验脚本里应包含"必须出现的正确值 + 不允许出现的旧错值"双向清单
(本次就是在校验里加了 chk("capex 98 亿" not in h) 才彻底钉死)。
十、验证基准
药明康德 SH603259,2018–2025(2026-09-21 复跑,作为回归基准)
| 年 | 收入 | 收入增长 | 扣非归母 | 扣非增长 | ROIC剔商誉 | ROIC含商誉 | 总资产 |
|---|---|---|---|---|---|---|---|
| 2018 | 96.1 | — | 15.59 | — | 29.06% | 24.57% | 226.7 |
| 2019 | 128.7 | 33.9% | 19.14 | 22.8% | 22.24% | 19.51% | 292.4 |
| 2020 | 165.4 | 28.5% | 23.85 | 24.6% | 21.99% | 19.79% | 462.9 |
| 2021 | 229.0 | 38.5% | 40.64 | 70.4% | 19.52% | 17.97% | 551.3 |
| 2022 | 393.5 | 71.8% | 82.60 | 103.3% | 25.76% | 24.22% | 646.9 |
| 2023 | 403.4 | 2.5% | 97.48 | 18.0% | 25.04% | 23.83% | 736.7 |
| 2024 | 392.4 | −2.7% | 99.88 | 2.5% | 22.34% | 21.49% | 803.3 |
| 2025 | 454.6 | 15.8% | 132.41 | 32.6% | 34.64% | 33.79% | 1,031.2 |
(单位:亿元。ROIC 单位:%)
扣非归母为财务摘要年度披露值(非单季加总,见易错点 10)。
> ⚠️ 2026-09-16 修订:扣非归母由「单季加总」改为「年度披露值」,
> 2021 由 41.2→40.64、2022 由 83.4→82.60,连带增长率 72.7%→70.4%、102.6%→103.3%、
> 2023 由 16.8%→18.0%。旧基准里的 41.2 / 83.4 是错的,不要再用。
> ⚠️ 2026-09-21 修订(旧准则科目补齐):annual_review.py 原先漏剔
> AVAILABLE_SALE_FINASSET(可供出售金融资产,2018 及更早属 ω 组投资性资产,新准则后并入
> 交易性金融资产/其他权益工具投资)。补齐后 2018 年 ROIC 剔商誉 27.43% → 29.06%、含商誉 23.40% → 24.57%
> (此前低估约 1.6pp)。2019 年起不受影响(新准则已无该科目)。
> 该修正与 roic-decomp、sources-uses 的同类修复同源,三处口径现已对齐。
> ℹ️ 与 roic-decomp 的 ≤0.05pp 差异属预期:本脚本取东财年度利润表,
> roic-decomp 走季度加总,聚合路径不同。2018 实测 29.06 vs 29.10,差 0.04pp,
> 在容差 TOL_PCT = 0.05 之内。对账时以本表为准(年度接口更贴近披露值)。
三重交叉验证:
1. ROIC 剔商誉与 roic-decomp 对齐(差 ≤0.05pp):29.06 / 19.52 / 22.34 / 34.64
—— 其中 2018 为 29.06(本表)/ 29.10(roic-decomp),其余年份逐位一致
2. 扣非归母与财务摘要年度值一致:2025 = 132.41 亿(摘要 132.4055695442 亿)
3. 总资产与 sources-uses 一致:2025 年末 1,031.2 亿(该 skill 记录 1,031 亿)
第五章(2018 末 → 2025 末):来源合计 995 亿,残差 0
左:净利润 575 / 外部股东投入 238 / 有息负债净增 81 / 经营性负债净增 105
右:分红 141 + 回购 50 + 资产增加 805
基准二:凯莱英 SZ002821,2016–2025(2026-09-16 实跑,第二个公司,用来验证通用性)
| 年 | 收入 | 收入增长 | 扣非归母 | 扣非增长 | ROIC剔商誉 | ROIC含商誉 | 总资产 | 员工数 | 员工增长 |
|---|---|---|---|---|---|---|---|---|---|
| 2016 | 11.03 | — | 2.46 | — | 27.23% | 27.23% | 24.45 | 2,040 | — |
| 2017 | 14.23 | 28.99% | 2.97 | 20.58% | 32.55% | 32.55% | 26.37 | 2,682 | 31.47% |
| 2018 | 18.35 | 28.94% | 3.69 | 24.22% | 20.64% | 20.64% | 31.85 | 3,291 | 22.71% |
| 2019 | 24.60 | 34.07% | 4.89 | 32.50% | 21.49% | 21.49% | 37.59 | 3,840 | 16.68% |
| 2020 | 31.50 | 28.04% | 6.44 | 31.79% | 21.98% | 21.82% | 71.58 | 5,477 | 42.63% |
| 2021 | 46.39 | 47.28% | 9.35 | 45.22% | 19.15% | 18.78% | 151.56 | 7,126 | 30.11% |
| 2022 | 102.55 | 121.08% | 32.31 | 245.44% | 38.10% | 37.35% | 182.39 | 9,719 | 36.39% |
| 2023 | 78.25 | −23.70% | 21.04 | −34.87% | 23.23% | 22.84% | 197.67 | 9,788 | 0.71% |
| 2024 | 58.05 | −25.82% | 8.50 | −59.63% | 5.82% | 5.73% | 192.89 | 9,595 | −1.97% |
| 2025 | 66.70 | 14.91% | 10.37 | 22.01% | 9.55% | 9.41% | 202.77 | 10,617 | 10.65% |
交叉验证:2025 年末总资产 202.77 亿 = H 股年报 202,774.66 万元 ✓;
第五章(2016末→2025末)来源合计 220 亿,残差 0;
员工数为期末在职人数(大智慧 F10),收入 ÷ 员工数=人均创收,
与同页「员工薪酬」表逐年核对一致(2016 54.08 万 → 2022 105.52 万 → 2024 60.50 万 → 2025 62.83 万)。
该公司第三章可拿到的多年序列(说明"年报之外还有哪些源"):
- 在手订单(亿美元):2021 18.98 / 2022 11.50 / 2023 8.74 / 2024 10.52 / 2025 13.85
(2023 来自公司业绩说明会材料,2021/2022 来自券商研报引用年报)
- 前五大客户占比:2016 80.08% → 2019 51.82% → 2022 72.00% → 2024 39.33% → 2025 35.87%
(2023/2025 来自理杏仁引年报,2024 为年报原文,早年来自中泰/国信研报)
- 反应釜体积(m³):2019 2,300 / 2020 2,800 / 2021 4,700 / 2022 5,300(券商研报;2023 起公司不再披露)
- 固定资产(期末)/ 当年 capex(亿元):2020 15.35/10.15、2022 36.22/21.51、2025 44.96/12.70
(2020 固定资产 +20.8% 与年报原文"15.35 亿、+20.72%"一致;2022 capex 21.51 亿、占收入 21% 与南京证券研报一致)
这家公司验证到的框架特性(值得记住,代表一类情形):
- 大订单脉冲型:2021–2023 单一超大订单把收入从 46 亿推到 102 亿又打回 58 亿。
→ 第一章表必须配合"剔除大订单"口径看,否则 2022 会被误读成能力基准。
- 剔商誉 ≈ 含商誉(2025 差 0.14pct)→ 内生型公司,无并购累赘。
与药明康德(2025 差 0.85pct)形成对照。
- 恒等式早期两年残差偏大(2016 0.40 / 2017 0.55 亿,约 IC 的 4.5%),
来自上市初期科目披露不全(租赁负债未单列)。残差非 0 时要先看是不是早期年份,
不要一律判定为口径错误。
基准三:康龙化成 SZ300759,2018–2025(2026-09-20 实跑,第三个公司)
| 年 | 收入 | 收入增长 | 扣非归母 | 扣非增长 | ROIC剔商誉 | ROIC含商誉 | 总资产 | 总资产增长 | 员工数 | 员工增长 |
|---|---|---|---|---|---|---|---|---|---|---|
| 2018 | 29.08 | — | 3.26 | — | 11.71% | 11.24% | 48.01 | — | 6,171 | — |
| 2019 | 37.57 | 29.20% | 5.05 | 55.14% | 14.61% | 13.98% | 99.35 | 106.93% | 7,393 | 19.80% |
| 2020 | 51.34 | 36.64% | 8.01 | 58.51% | 20.13% | 17.43% | 119.09 | 19.87% | 11,012 | 48.95% |
| 2021 | 74.44 | 45.00% | 13.41 | 67.46% | 19.19% | 15.30% | 183.89 | 54.42% | 14,923 | 35.52% |
| 2022 | 102.66 | 37.92% | 14.21 | 6.01% | 13.95% | 11.22% | 204.93 | 11.44% | 19,481 | 30.54% |
| 2023 | 115.38 | 12.39% | 15.14 | 6.51% | 11.76% | 9.64% | 264.77 | 29.20% | 20,295 | 4.18% |
| 2024 | 122.76 | 6.39% | 11.08 | −26.82% | 9.86% | 8.21% | 239.27 | −9.63% | 21,370 | 5.30% |
| 2025 | 140.95 | 14.82% | 15.38 | 38.85% | 10.68% | 8.89% | 270.94 | 13.23% | 25,088 | 17.40% |
第五章(2017末→2025末):来源合计 252 亿,残差 0;
左:累计净利润 98 / 外部股东投入 59 / 有息负债净增 57 / 经营性负债 37
右:分红 19 + 回购 2 + 资产增加 232
这家公司验证到的框架特性(代表"重资产扩张型 CXO"这一类):
- 两条腿同时退:ROIC 从峰值 20.13%(2020)掉到 9.86%(2024),
净利率 −4.95pct(18.16→12.62)与周转率 −0.26 次(1.16→0.89)同步下滑。
与凯莱英的"大订单脉冲"(净利率主导、单点冲击)形成对照——
同步下滑说明是资本扩张周期本身的性质,而非某项业务拖累,判断结论完全不同。
- 营运资本周转率腰斩是先行信号:10.10 → 4.98 次(−51%),比 ROIC 更早、更陡。
客户结构从 Biotech 转向 MNC 时账期会变长,这条线要单独看。
- 商誉驱动的双口径分化:商誉 1.34 → 35.86 亿(2020–2022 集中并购),
剔/含商誉差距 2021 年峰值 3.89pct 后收窄到 1.79pct。差距大的公司必须用剔商誉口径。
- capex 单季是本章最有价值的曲线:2022Q4 峰值 7.51 → 2024Q2 谷底 4.52 →
2026Q2 新高 7.89 亿,完整讲出"扩张—收缩—再扩张"。capex 比收入更早 2–6 个季度暴露转折。
- 收入与利润剪刀差:2026Q2 收入 +20.19% 而扣非仅 +1.58%(Q1 还是 +17.45%)。
季度段的真正价值就在这类信号上——年度表完全看不到。
基准四:英科医疗 SZ300677,2013–2025(2026-09-22 实跑,第一个「上市前无季度数据」的公司)
| 年 | 收入 | 收入增长 | 扣非归母 | 扣非增长 | ROIC剔商誉 | ROIC含商誉 | 总资产 | 总资产增长 | 员工数 | 员工增长 | 当年股价涨跌幅 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 2013 | 7.13 | — | 0.13 | — | — | — | 7.04 | — | — | — | — |
| 2014 | 8.85 | 24.0% | 0.30 | 136.7% | 12.44% | 12.44% | 6.57 | −6.8% | — | — | — |
| 2015 | 9.98 | 12.8% | 0.82 | 175.4% | 15.54% | 15.54% | 7.54 | 14.9% | — | — | — |
| 2016 | 11.83 | 18.5% | 0.87 | 6.3% | 12.90% | 12.90% | 11.64 | 54.3% | 3,194 | — | — |
| 2017 | 17.50 | 48.0% | 1.45 | 66.0% | 17.90% | 17.90% | 16.58 | 42.5% | 3,600 | 12.7% | +79.0%* |
| 2018 | 18.93 | 8.1% | 1.67 | 15.3% | 13.09% | 13.09% | 24.25 | 46.2% | 3,523 | −2.1% | −43.7% |
| 2019 | 20.83 | 10.1% | 1.69 | 1.0% | 12.37% | 12.37% | 29.92 | 23.4% | 3,671 | 4.2% | +13.7% |
| 2020 | 138.37 | +564.3% | 70.06 | +4045.0% | 252.25% | 252.25% | 129.35 | +332.3% | 6,503 | 77.1% | +1405.2% |
| 2021 | 162.40 | 17.4% | 73.86 | 5.4% | 119.10% | 119.10% | 206.25 | 59.5% | 9,115 | 40.2% | −46.5% |
| 2022 | 66.14 | −59.3% | 4.60 | −93.8% | −0.81% | −0.81% | 214.79 | 4.1% | 8,824 | −3.2% | −52.9% |
| 2023 | 69.19 | 4.6% | 0.87 | −81.0% | −1.47% | −1.47% | 270.79 | 26.1% | 10,939 | 24.0% | +10.3% |
| 2024 | 95.23 | 37.6% | 11.67 | +1239.1% | 7.76% | 7.76% | 346.31 | 27.9% | 10,982 | 0.4% | +7.9% |
| 2025 | 99.26 | 4.2% | 3.97 | −66.0% | 6.22% | 6.22% | 400.90 | 15.8% | 12,941 | 17.8% | +49.0% |
(单位:亿元;ROIC、各增长率、股价涨跌幅单位:%;员工数:人)
这一跑验证/暴露的框架特性(代表"超级周期 + 次新股"这一类):
- 首个必须走年度利润表兜底的公司:季度接口只到 2016Q3 →
2013–2016 四年由stock_profit_sheet_by_yearly_em兜底(2016 若按季度加总只有 6.4 亿,错差点近一倍)。 - 首个"首年 ROIC 留空"的公司:2013 年缺 2012 年末资产负债表 → 期初 IC 无值 → ROIC 填「—」,
其余指标照常(这就是"缺年份不再致命退出"改动的用意)。 - 超级周期把 ROIC 打到 252%:2020 年 NOPAT ≈ 55 亿而 IC 均值仅 ≈ 22 亿。
这个数算术上没错,但完全不可持续——报告里必须显式标注"周期极值",
不能当作能力基准(同类情形见基准二凯莱英的"大订单脉冲")。 - ROIC 连负两年(2022 −0.81% / 2023 −1.47%):说明周期下行时连投入资本都挣不回来;
注意此时总资产还在涨(214.8 → 270.8,+26.1%),资产扩张与 ROIC 方向相反——
正是「第 8 列总资产增长是 ROIC 前哨」的又一例证。 - 剔商誉 ≡ 含商誉(13 年逐年完全相等):商誉 13 年均为空 → 纯内生,无并购累赘。
- 新列「当年股价涨跌幅」直接可用:2020 年 +1405.2% 与基本面同向爆表;
2021 年收入还在 +17.4%、股价 −46.5% —— 市场提前一年定价了周期见顶,
这正是新增该列的价值的直接证据(只看财务表会晚一年)。 - 季度段确认 2026 年在加速:2026Q2 收入 40.22 亿(+66.2%)、扣非 8.79 亿(+472.0%),
2026H1 累计 69.09 亿(+40.6%)、扣非 10.61 亿(+165.0%)——
与 2025Q4 扣非为负(−0.88 亿)形成剧烈反转,是年度表完全看不到的信息。
跑新公司后的自检清单:
- [ ] 第一章首年增长率为 —(不是 0)
- [ ] ROIC 剔商誉 ≥ 含商誉(剔商誉分母更小,回报率应更高);商誉大时差距应明显
- [ ] 扣非归母有值(若为 — 说明取数失败,见易错点 10)
- [ ] 第四章恒等式 NOWC + NLOA = IC 残差 ≈ 0(只内部校验,不进报告)
- [ ] 第五章残差 = 0,且 T 字表是左右 grid 布局(不是上下两张表)
- [ ] 第二章各口径占比之和 = 100%
- [ ] 第三章若为空,是确无此类项目,不是漏写
- [ ] 第三章每个项目至少 5 年,且每行标了来源(只有一两年=取数没做够)
- [ ] 第一章有员工总数与员工增长两列,且 收入 ÷ 员工数 与 F10 人均创收对得上
- [ ] 正文里每个"累计/合计"数字都用分项加总复核过(见易错点末尾的 98 亿教训)
- [ ] 所有同比/环比数字都用脚本算过,不是凭"看起来在低位"推断的(易错点 25)
- [ ] 起始年已确认有财报数据(易错点 26:次新股从上市前一年起,报缺年份就逐年往后推)
- [ ] 跑新公司前查过各年季度条数(易错点 29:某年 <4 条会静默把半年当全年,须走年度接口兜底)
- [ ] 股价列是后复权、上市前年份填「—」(易错点 31:不复权会把除权跳空算成暴跌)
- [ ] 第五章筹资明细含「支付其他与筹资活动有关的现金」一行(易错点 27)
- [ ] 左侧目录无断链:shell.py 生成时已自动校验;手工改过 HTML 就再跑一次
check_anchors(),删过章尤其要查(目录必须由 build_nav() 生成,不是手写)
变更记录
- 1.7.1(2026-09-22,用户要求):第四章 HTML 去掉两张表(与 1.7.0 并行施工,已合并)。
用户原话「参考 · 规模量(均值,亿元)和对账 · 恒等式 NOWC + NLOA = IC(剔商誉,平均,亿元)
这两部分没有必要要」。
① 源头修复:roic-decomp→ 1.1.0,roic_decomp.py的 HTML 输出删掉这两张表;
终端的【对账①】【对账②】【明细】【参考】全部保留(残差 >1 亿仍告警)——
恒等式是发现"科目未归类"的唯一防线(药明首跑 2024 残差 +25 亿就靠它抓到),
删了会静默出错,只是不再进交付物。
② 4.3 节与易错点 14 更新为「源头已修、拼接不用再手工删」。
③ 新增易错点 38:别只在拼接说明里写"记得删",要改生成源头 ——
恒等式对账表 2026-09-16 就记过,源头没动,导致用户 9-22 重复提(这次连规模量表一起)。
④ 并行编辑提醒:本次与 1.7.0(导航外壳固化)同时编辑同一个 SKILL.md
(各自会话独立进行)。写入前先重读最新内容;极易错点是易错点编号撞车 ——
我原本新增的 36 与 1.7.0 的 36 重号,已改为 38。
规则:改这类被多会话共用的 skill 文档,动手前先grep "^version"看版本,
改完再确认没覆盖别人的条目。 - 1.7.0(2026-09-22,用户明确要求):报告外观固化为可复用外壳。
用户原话「这个页面的左侧有一个导航栏,我认为这是一个非常好的功能,我们 skill 里边有写这个
内容吗能把它固化下来吗」——查证结果:SKILL.md 全文只有 1 处提到导航
(@media print里的nav{display:none}),HTML 结构、CSS、scrollspy 一个字都没写,
templates/report_skeleton.md是纯 Markdown、一个标签都没有 → 每次做报告都靠模型凭记忆重写,
必然长歪。本次固化:
① 新增templates/report_shell.html——从 东富龙 300171 2010–2025 实跑稿抽出完整外壳
(:root 变量 /.wrap两栏栅格 / nav / hero / KPI / 表格 / 两个媒体查询),
留SHELL:TITLE/SHELL:NAV/SHELL:BODY三个占位;经归一化比对,
CSS / scrollspy JS / 正文与原稿逐字一致;
② 新增scripts/shell.py:render()拼装、build_nav()扫正文实际锚点自动生成目录
(从结构上消灭「删章漏删锚点」的断链)、check_anchors()反查断链,并支持--nav-only/
--from-report;CLI 生成时自动校验,有断链即报错退出;
③ 新增 锚点命名规范(c0摘要 /c1–c6章 /ap附录 /cN-M二级;
div类无标题锚点必须data-nav="文案"才进目录;data-nav="-"显式排除);
④ 新增 易错点 36(外壳 CSS 注释里不能写带尖括号的标签名,会让「抽正文」正则先误配——
本次实际踩到)与 易错点 37(填充一律str.replace(),不能.format(),CSS 大括号会 KeyError);
⑤verify.py新增 G 层「HTML 外壳与目录」静态自检(含「删章不留断链」「死链能被抓到」
「标记重复会报错」三个负向测试)+REQUIRED补两文件。
⑥ 顺手修掉 README 里与 ENV.local.md 直接矛盾的过时说法:原文写「必须用系统 Python 3.14,
托管版 Python 3.13 没装 akshare」,而本机 ENV.local.md 明确指向托管 venv——机器相关内容
一律只写 ENV.local.md,README 改为指向它。
验证:verify.py --fullPASS 140 / FAIL 0(静态 A/B/G 96 项);外壳保真度另做逐字比对,
CSS / scrollspy JS / 正文与 东富龙 实跑稿完全一致。 - 1.6.0(2026-09-22,用户明确要求):交付形态收敛为「只有一个 HTML」。
用户原话「以后只生成一个 html 文件就行了,别的不用了」。
① 新增「交付约定」(开头醒目提示 + 第五章末详规):一次复盘只交付一份合成 HTML,
六章合一、表格全内联;三个脚本的产物(现象表 CSV/HTML、ROIC 拆解 HTML、T 字表 HTML)
一律落临时目录、不交付,但也不要删(verify.py与数字溯源依赖);
② 原「导出 PDF 必须做的两件事」降级为「可选,仅用户显式要求 PDF 时」,
但 T 字表@media print强制左右布局这条保留——将来做 PDF 仍会踩;
③ 「完整流程」代码块改为$TMP/$OUT两个变量,第 6 步单列合成与交付;
④ frontmatter 的summary/description删掉「HTML+CSV」「内置 PDF 导出」表述
(否则召回时会把旧口径带回给模型)。
新增易错点 35。自检 PASS 100 / FAIL 0。 - 1.5.1(2026-09-22,英科医疗完整六章实跑回写):修掉 1.5.0 留下的四个坑,全部来自
英科医疗这次真实跑完整六章的过程。
① HTML 年度段漏改(1.5.0 并行编辑被覆盖的遗留):第 11 列在 HTML 里只渲染成裸数字,
现已补上%、上市首年*/ 末年YTD标记与涨跌着色(终端那一处本来就是对的,所以更难发现);
② 新增股价缓存兜底<输出目录>/price_{6位数字代码}.csv——东财 K 线接口会被代理
间歇性挡住,重试全败时整列会空;成功时写缓存、失败时自动读回并打印[info] 改用缓存;
③ 文件名约定写进文档:events_{6位代码}.csv、price_{6位代码}.csv等一律用
6 位数字代码(300677),不是SZ300677——用错会静默找不到;
④verify.py三处 CSV 选择器的排除前缀由events_/emp_扩为
events_/emp_/price_,否则新缓存会被当成结果 CSV 取走(C/D 层报
KeyError: '收入(亿元)')。
新增易错点 33、34,并把易错点 32 补上这个真实受害案例。自检 PASS 100 / FAIL 0。 - 1.5.0(2026-09-22):第一章指标由十一项扩为十二项——新增第 11 列
「当年股价涨跌幅」(后复权,年末÷上年末−1;上市首年以首日收盘为基数加*;
末年 YTD 加YTD;上市前年份填「—」),位置在「当年重大事件」之前(用户 2026-09-22 要求)。
配套三处修复:
① 新增年度利润表兜底通道——某年季度记录 <4 条时整行改取
stock_profit_sheet_by_yearly_em(英科医疗 2013–2016 靠它才出得来;
否则 2016 会把半年 6.4 亿当全年,静默错误);
② 缺年份不再致命退出,改为分层降级(利润表缺 → 该年收入/扣非/ROIC 留空;
首年缺期初资产负债表 → 该年 ROIC 留空);
③ 股价接口加重试(实测偶发 RemoteDisconnected)。
新增易错点 29–32、新增「基准四:英科医疗 2013–2025」。
季度段新增该列并填「—」(列保留,理由同员工两列)。 - 1.4.1(2026-09-21,双机合并):以本机版(含 2026-09-20 康龙化成实踩的易错点 25/26/27、
康龙化成基准三数据表、检查清单 3 条,以及新增的港股复盘脚本scripts/hk/hk_review.py)为基底;
并入办公室版更新的PUBLISH_STATUS.md(记录 v1.4.0 已通过审核、线上正式生效)。
机器相关内容全部剥离:本机 Python 路径、输出目录、本机工具链坑(Bash PATH 劫持、
PowerShell 长输出卡死等)→ 统一迁至ENV.local.md;SKILL.md / README / references 中
原有的C:\Users\<用户名>\...、D:/tmp等硬编码路径已全部改为~/.workbuddy/skills/...、
<输出目录>、$env:USERPROFILE等可移植写法。
清理:scripts/hk/t1.txt(爱康医疗取数测试输出,脚本未引用)已移出 skill 至备份目录。
口径修复:scripts/annual_review.py补入旧准则科目AVAILABLE_SALE_FINASSET
(可供出售金融资产,属 ω 组),2018 年 ROIC 由 27.43/23.40 修正为 29.06/24.57
(此前低估约 1.6pp);同步更新verify.py的BASELINE与「十、验证基准」表。 - 1.4.0 / 1.3.1:见
PUBLISH_STATUS.md。
一句话记忆
> ROIC 说这门生意好不好,g 说还在不在长;第二章拆 g,第四章拆 ROIC,第五章看钱怎么在两者之间流转。
>
> 创新药别用——它的投入在今天,收入在很远的未来。