原始内容
name: report-workpaper description: > 把券商研究报告的每一段话找到网络数据来源,做成 Excel 底稿(左侧报告段落截图, 右侧来源网页截图+红框高亮支撑句+可点击标题链接,逐行对齐)。半自动:逐段联网搜索、 列候选给用户确认,再截图组装。表格内容单独建 sheet 写来源。 触发:底稿、做底稿、数据来源、报告找来源、research workpaper、source check、 把报告每段找来源、合规底稿。 metadata: author: lihonghao version: "0.3"
report-workpaper · 研报底稿生成
把一份 .docx 研报的某一章/节,逐段找到网络数据来源,产出一个 Excel 底稿:
┌──────────────────────┬───┬─────────────────────────────────────────────┐
│ 左:报告段落截图 │ │ 右:证据列 —— 每一句话一个来源,从左到右依次排 │
│ (红色小标题+加粗首句) │ │ 句1来源→ 句2来源→ 句3来源→ …(每个上方=可点击标题)│
│ 一段 = 一个横向条带 │ │ 红框圈出该句在原文里的支撑文字 │
└──────────────────────┴───┴─────────────────────────────────────────────┘
排布规则(用户定义,务必遵守):左侧报告内容从上往下逐段排;右侧证据从左到右 依次放——报告里的每一句话配一个来源截图,按句子顺序左→右排满;这一段所有句子都有来源后, 再下移到下一段重复。一段有 N 个可考证句子 → 右侧就有 N 个来源横向并排。
- 每个三级小节(如 4.3.1)= 一个 sheet,sheet 名即小节号。
- 节内的表格/图另起 sheet(sheet 名=表/图标题),单独写来源(见 Phase 2)。
逐句必须找源(默认每一句都要核实,不要轻易留白)
默认对报告里的每一句话都要做数据源核实、找到能支撑它的材料——这是用户的硬要求。 不要因为"看起来像观点"就整段跳过(这是早期版本常犯的偷懒)。具体做法:
- 事实/数据句(数字、占比、时间、政策、公司动态、产品、订单、第三方测算)→ 必须配权威逐字源。
- 总述/承上启下/topic 句("深耕…铸就全球头部Tier1""股权治理结构稳定")→ 拆出其中的事实内核去配源 (如"全球头部Tier1"→招股书"全球汽车零部件排名第41""全球第二被动安全";"股权治理稳定"→实控人持股结构)。
- 战略/框架句("三大战略结构清晰""稳基石/强增长/拓远期")→ 找公司公开披露的对应战略表述/业务布局做支撑 (官网战略、招股书业务概览、业绩会、定点公告),而不是直接判定"本所观点、无需来源"。
- 真正的纯主观判断("我们认为/我们看好/预计…将"且无任何可外部验证的事实内核)→ 才可留白, 但必须在备注里写明:已尝试的检索角度 + 为何无外部源,不能默默空着。
- 只有在多角度穷尽检索后仍找不到真实逐字源,才留
sources:[],并在核查备注标"留白原因+已试关键词"。 宁可一句话配一个"支撑其事实内核"的源,也不要整段空白。逐句覆盖率是本 skill 的核心质量指标。
例外·财务数据可不溯源(用户约定):典型的大片财务数据——收入/营收、利润/净利/毛利率、股权与持股结构——不需要本 skill 找源, 因为用户用同花顺(iFinD) / 万德(Wind)自己做溯源。这类句子在底稿对应位置留空即可,并在核查备注标注"财务/股权数据→按约定由 iFinD/Wind 溯源,不在底稿核查范围"。 注意边界:① 仍需逐句核的是非财务数据——产品、订单/定点金额、市占率与行业地位(定性)、战略与业务布局、并购/设立/控股等公司事件、子公司分工、技术与合作、行业规模/渗透率/政策。 ② 订单/定点金额、市占率虽带数字,但不在标准财报/Wind 口径内,仍要找源;只有进财报三表与股东名册的收入/利润/股权才豁免。 ③ 报告里财务数字与官方"对不上"的,仍可在核查备注提示(供用户用 iFinD/Wind 复核),但不必为其找网页红框源。
环境依赖(本机已具备)
python3 + openpyxl + Pillow + playwright(python) + 已装 chromium。无需 LibreOffice。
fetch_verbatim.py 只用 stdlib(urllib),无额外依赖、无需 MCP。
联网遵循用户的 web-access 规则:① 搜索用 WebSearch(找候选 URL);② 取页面逐字正文用本
skill 的 fetch_verbatim.py(Jina Reader r.jina.ai,免 key 20 RPM / 设 JINA_API_KEY 500 RPM)——
不要用 WebFetch 挑红框短语,它会改写原文;③ 截图用 capture_source.py(自带 headless chromium);
登录态/反爬站点(微信公众号、小红书、登录后 PDF、Jina 取回 403 的页)→ 让 Chrome 开
--remote-debugging-port=9222(或经 web-access 开启),再给 capture_source.py 传
--cdp http://localhost:9222 复用用户已登录浏览器读实时 DOM。
脚本路径(关键,全局可用):本 skill 可在任何项目/目录触发。运行下面命令前先固定路径——
SK="$HOME/.claude/skills/report-workpaper/scripts" # 或 ${CLAUDE_SKILL_DIR}/scripts
下文命令里的 scripts/X.py 一律按 python3 "$SK/X.py" 来跑(不要假设当前目录就是 skill 目录)。
所有产物(截图、manifest、xlsx)写到当前工作的项目目录里,不要写进 skill 目录。
换机器/换系统时:需自行装
openpyxl/Pillow/playwright+chromium,并确认有中文字体 (本机用 macOS 自带 PingFang SC;Linux/Windows 需改render_paragraphs.py字体)。
工作流(半自动·逐段确认)
Step 1 — 选定章节
python3 scripts/extract_report.py REPORT.docx --list-headings
把树状目录给用户,确认要做哪一节(如「4.3 缺电」)。标题样式按 styles.xml 的 outlineLvl 识别(0/1/2=H1/H2/H3),跨报告通用。
Step 2 — 抽取该节正文
python3 scripts/extract_report.py REPORT.docx --h2-contains 缺电 --label 4.3 \
--out section.json
得到 section.json:每个小节(4.3.1/4.3.2…)的标题、各段正文(含逐 run 粗体)、
以及节内出现的图/表标题(供 Phase 2 建表格 sheet)。
Step 3 — 渲染左侧报告段落截图
python3 scripts/render_paragraphs.py section.json --outdir left_imgs/ \
--out-manifest left_manifest.json
每段渲染成 PNG(红色小标题+加粗首句+两端对齐正文,仿报告样式;dpr=3 高清)。
Step 3.5 — 逐句覆盖清单(确保不漏句)
python3 scripts/split_sentences.py section.json --out coverage.json
把每段切成句子并标 factual/opinion,作为找源清单——报告里每一句话都要在右侧有对应来源, 按句序左→右排满该段,再下移。即使被标成 opinion 的句子也要先按上面「逐句必须找源」去拆事实内核找源, 只有穷尽检索后确实无外部源的纯主观判断才留白(并在备注写明已试角度)。不要看到 opinion 标签就跳过。
Step 4 — 逐句找来源(与用户确认)
推荐打法·逐句覆盖用「多 agent find→对抗verify」工作流(实测对整章/逐句覆盖最稳,2026-06 在均胜1.1验证) 句子多时不要自己一句句串行查。先把主源拿下,再并行核每一句:
- 先定主源:找到标的公司的 H股招股书/聆讯后资料集 或 年报/季报(港交所 hkexnews、巨潮、公司官网 IR)。 一份招股书往往一页就覆盖市场规模/市占率/ASP/产品矩阵/公司排名/历史沿革多句——下载后用
pdftotext按页检索关键数字定位页码,pdftoppm渲染该页整页图+手标(PDF 红框留人工)。这类"大片定性/产品/排名"事实优先走招股书一手。- 再逐句并行核:对每个非财务句子派一个 agent,两段流水线——
- find:给它[公司背景+源纪律+已知线索URL],要它找一个逐字可验证的权威源,亲自
fetch_verbatim.py --find校验 ok:true 才回传;- verify(对抗):另一个 agent 独立复核——重跑
--find确认逐字命中、并判断该源是否真支撑该句事实内核(不是擦边),不轻信 find。 用 Workflow 工具的pipeline(SENTENCES, findFn, verifyFn)一次跑完(每句 find 完即 verify,无需等齐);返回结构化{sid, verdict, final_url, final_phrases, note}。- 分类落账(写进"逐句覆盖报告",见 Step 5):
- support:源逐字支撑事实内核 → 直接红框。
- partial:事实内核已逐字坐实可红框,但报告的概括/对仗措辞或个别细节系作者提炼(如"三大战略""稳基石/强增长/拓远期"、eVTOL、微电机等)→ 红框框事实句,备注提示正文软化。
- 豁免:收入/利润/股权等财务数据 → 留空+备注(iFinD/Wind)。
- 留白:多角度穷尽仍无权威源 →
sources:[]+备注"已试关键词"。 目标:非财务句的真·留白=0。
对每个 factual 句子(不只是每段),若不走上面工作流而手动来:
- 用
WebSearch/firecrawl_search搜该句的关键数字/事实。密集查找时可并行派多个 general-purpose / Explore agent,每个包 1 个事实点,要求其**实际抓取页面核实"逐字短语真实存在"**再回传 URL+短语。 - 给用户列候选来源(标题+链接+说明支撑哪一句),让用户挑/否决/补链接(半自动核心,不替用户拍板)。
- 确定红框高亮的原文句子:必须是来源网页里逐字存在的短句(含数字,如「5,427 data centers」
「65GW」「doubles roughly every five months」)。注意全角/半角、"基荷"≠"基础负荷"、花引号。
不要信
WebFetch返回的"原文"——它会改写,挑出来的短语在页面上往往不逐字存在、红框就框不上。 改用fetch_verbatim.py先取页面逐字正文(Jina Readerr.jina.ai,免 key、stdlib,无需 MCP), 从中挑短语,再用--find校验该短语确实会被命中(校验逻辑和 capture_source 的 HIGHLIGHT_JS 完全一致):
python3 scripts/fetch_verbatim.py --url URL --out page.md # 取逐字正文,从中挑短语
python3 scripts/fetch_verbatim.py --url URL --find "5,427个" --find "占全球总量的45%"
# ok:true=会命中可直接截图;unmatched=换更短的逐字片段;
# fold_only=短语存在但全/半角不一致(如 5,427 vs 5,427),改成页面里的写法
# 高频批量可设环境变量 JINA_API_KEY(免费,500 RPM);403/反爬页改走下面 --cdp 读实时 DOM
- 截图(CDP 抓取,自带红框):
python3 scripts/capture_source.py --url URL --out src_imgs/431_p1s2_xxx.png \
--title "来源标题 [S2]" \
--highlight "逐字支撑句1" --highlight "逐字支撑句2"
# 登录态站点加: --cdp http://localhost:9222(复用已登录 Chrome)
# 找不到精确句时加 --full 出整页图,红框留给用户手标
# 可调: --crop-w 760(强制横裁宽度) --context 130(上下文行) --max-h 1500(高度上限)
裁剪是“贴着高亮句”的(不是整页):横向裁到高亮所在段落/正文列(≈600–800px),纵向只取
高亮行 + 上下各一两行上下文。原因:底稿把每张源图按固定宽度展示(build_workpaper SW),
屏幕上文字高度 ≈ 网页字号 × SW ÷ 裁剪宽度——整页宽裁会把 17px 的字缩成 ~8px 看不清。
一张源图只配一句话:--highlight 尽量给一句(或同一段里相邻的两三个短语);
别把相隔很远的多个短语塞进一次截图,否则裁剪会拉得很高、字很小。脚本会把落在裁剪框外的
高亮放进返回 JSON 的 off_crop——看到 off_crop 非空就拆成多次截图(每句一张,左→右并排)。
返回 JSON 报 matched/unmatched/off_crop,没命中就换更短的逐字片段重试。
隐藏 tab / 滚动渐显动画会截出空白:有的官网页面把文字放进未激活的 tab/手风琴 (
display:none、定位屏外),或用滚动渐显动画(初始opacity:0/与背景同色,滚到才显形)。 这些文本节点在 DOM 里能逐字匹配到、红框也会画上,但渲染区是空白 → 截出来是「白底+空黄框」。 改用该页默认就可见的逐字句,或换一个把同一事实写在正文里的来源(官网news/info/*新闻页、年报摘要、财经媒体正文)。 均胜官网已实测的空白页→替代源(直接用,别再踩坑):
about.html发展历程时间轴(2004起步/涡轮增压进气/空气管理)= 空白 → 换news/info/710.html("成立于2004年""核心产品涵盖空气管理系统、发动机进气系统",正文可见)。driving.html智能驾驶 tab(均联智行/L2++至L4)= 空白 → 换news/info/1251.html("均联智行发布首款智能驾驶域控制器")或招股书智能网联页。 截完务必逐张抽看确认不是空白——ink%粗筛会漏掉这种「空框」图(黄框本身有像素,ink<3.5% 基本是空白), 最稳的是把所有源图拼成缩略图 montage 一眼扫一遍(见 redo_1.1/capture_jobs_11.py 末尾的 montage 写法)。红框落点偏移/空框:个别站点(如东方财富「财富号」
caifuhao.eastmoney.com)DOM 结构异常, 红框会画到右边距的空白处或整体偏移几个字;逗号串里的裸数字(“497.93亿元、…”跨行)也易框错。 解决:①--highlight用带前后词的整句片段(如「全年实现营业收入558.6亿元」而非「558.6亿元」); ② 换一个红框正常的来源(公司官网/上证报·中国证券网/证券时报/第一财经/盖世 实测都正常)。
Step 5 — 组装底稿 + 预览
把每段拼进 manifest(schema 见下),然后:
python3 scripts/build_workpaper.py build_manifest.json --out 底稿_4.3.xlsx \
--preview-dir preview/
preview/preview_<label>.png 是所见即所得预览图(和 Excel 同坐标),先给用户看,
确认无误再交付 .xlsx。
配套交付·逐句覆盖报告(强烈建议,证明"每句都配了源"):另出一个小 xlsx,逐句一行列
段·句 | 报告句要点 | 数据来源(可点击) | 已验证逐字短语 | 支撑度(support绿/partial黄/豁免灰) | 备注(口径/软化/留白),
顶部"说明"sheet 写覆盖统计(如"19句→support12+partial5+豁免2+留白0")与源纪律。
模板见 redo_1.1/build_coverage_11.py;差异/对不上的另出"核查备注"见 ch2/build_notes2.py(可合并多章)。
Phase 2 — 表格 sheet(暂缓,按需开启)
节内每个表/图另起 sheet(名=表/图标题),把表格每一格/每条数据的来源写进去;
在对应正文 sheet 用 {"type":"sheet","title":"见表: …","target":"<表sheet名>"}
建跨表跳转链接。当前版本先做文字+图片,表格按用户节奏再加。
build_manifest.json schema
{
"section_label": "4.3",
"section_title": "缺电:逻辑持续演绎 海内外算力景气共振",
"sheets": [
{
"label": "4.3.1",
"title": "北美缺电背景:需求端大幅增长与供给端多重约束的失衡",
"rows": [
{
"left_img": "/abs/left_imgs/4.3.1_p1.png",
"sources": [
{"title": "来源标题(会做成可点击链接)",
"url": "https://…",
"img": "/abs/src_imgs/431_p1_xxx.png"}
]
},
{"left_img": "/abs/left_imgs/4.3.1_p2.png", "sources": []}
]
}
]
}
- 一段未找到来源 →
"sources": [](底稿会留白,提示待补)。 - 一段多来源 →
sources多个,右侧自动横向并排。 - 跨表链接 → 源对象用
{"type":"sheet","title":"见表: 表10","target":"表sheet名"}(无 img)。
布局参数(scripts/build_workpaper.py 顶部常量,可调)
LW 左图宽 / SW 来源图宽 / GAP,SGAP 间距 / TITLE_H 标题带高 / ROW_GAP 段间距。
像素网格 ROW_PX=20,COL_PX=64=正常大小单元格(width=(COL_PX-5)/7 精确映射回像素);
showGridLines=True 显示网格线,底稿看起来就是普通表格。图片用 twoCellAnchor 锚真实单元格
- 子单元格 EMU 偏移定位,所以格子大小不影响图片对齐。段间分隔线
SEP_SIDE=红色 medium。
已知限制(用户已确认可接受)
- 微信公众号 / 小红书:这两类来源不需要,直接跳过,不必为它们费力截图。
- PDF 来源:chromium 能打开但 PDF 阅读器里无法逐字定位红框 → 用
--full出整页图, 红框留给用户手标即可,不必强求自动框选。 - 来源优先级:① 除了国内券商友商研报(同业研报,如东吴/中信等)不能引,其余来源
(官方/外媒/IEA/大行/公司财报/行业媒体/199IT/前瞻网/OFweek 等)都可以。
② 优先中文源:若中文数据源能充分且很好地覆盖该论点(每一句话),就优先用中文源;
仅当中文覆盖不足/质量不够(如北美电力等议题一手数据多为英文)时,再用外文源。
③ 主源优先:公司类章节先拿招股书/年报/季报/公告一手(市占率/市场规模/产品/排名/沿革常一页覆盖多句),
官网新闻页(
news/info/*)次之,权威媒体(上证报/证券时报/第一财经/光纤在线/银柿/盖世/中证网/同花顺/21财经)再次之。
注意事项
- 来源必须真实且确实支撑该句——这是合规底稿,宁缺毋滥;找不到就留
sources:[](底稿留白=待补), 绝不编造链接。不确定就列候选给用户判断。 - 红框靠"逐字匹配"实现:
--highlight必须用来源页里真实存在的原文(注意全角/半角、"基荷"≠"基础负荷"、 花引号等),不是报告里的转述。关键修复:早期用WebFetch读页面挑短语——它会改写原文, 导致挑出的短语在页面上不逐字存在、红框框不上。现在统一先用fetch_verbatim.py(r.jina.ai,免 key) 取逐字正文再挑短语,并用--find校验(其归一逻辑与 HIGHLIGHT_JS 一致,还会用fold_only提示全/半角不符)。 - 图片锚定(关键,勿回退):组装时图片必须用
twoCellAnchor锚到真实单元格(见 build_workpaper.pycell_marker)。早期版本用oneCellAnchor锚 A1+大像素偏移,macOS Excel/Numbers 会把偏移截断导致所有图挤在左上角重叠——已修复,不要改回去。 - 截图勿用 full_page,且要“贴着高亮句”裁:高 dpr 下整页截图会变成上亿像素(PIL 解压炸弹)
且广告页会卡 "waiting for fonts"。capture_source.py 用 CDP
Page.captureScreenshot+ clip, 横向裁到高亮所在正文列、纵向只取高亮行+少量上下文(dpr=3)——这样底稿里源图的字才够大。 不要改回 full_page,也不要把裁剪放宽到整页宽(会让字缩小到看不清)。多个相距远的短语拆成多张。 - 大文件:原始底稿可能上百 MB(满是截图),新建独立
.xlsx,不要直接改用户的原文件。 - 渲染/截图字体用 PingFang SC(macOS 自带);换机器需确认中文字体。
源发现(WebSearch + Firecrawl MCP 已接入)
逐字正文用 fetch_verbatim.py(Jina Reader,免 key、已默认接入)。源发现有两条互补通道:
WebSearch(内置,默认):通用闭环,无需任何配置。- Firecrawl 远程 MCP(已接入,2026-06-25 配好并实测 ✓):中文发现更强。提供
firecrawl_search(发现,Bing/Google 风格,实测能直出新浪/东财/上证报等中文源)+firecrawl_scrape(逐字 markdown/rawHtml)。- 配置:
claude mcp add --transport http firecrawl https://mcp.firecrawl.dev/v2/mcp --header "x-firecrawl-api-key: fc-..."(key 写进 header;远程服务,无需 docker;免费档 ~1000 credits/月)。已加在本机 local config,重启会话后firecrawl_search/firecrawl_scrape才出现在工具列表。 - 源纪律照旧(重要):Firecrawl 搜索会混入研报库结果(实测
pdf.dfcfw.com/东财研报、fxbaogao等)。 这些只能当线索,必须主动剔除;真正取源与红框仍走官网/巨潮公告/上证报·中国证券网/证券时报/第一财经/新浪/光纤在线/盖世/银柿等权威源,并经fetch_verbatim.py逐字校验。
- 配置:
- SearXNG MCP(备选,真免 key 但需自建 docker):
docker run -d -p 8080:8080 searxng/searxng(settings.yml 开json),.mcp.json加{"mcpServers":{"searxng":{"command":"npx","args":["-y","mcp-searxng"],"env":{"SEARXNG_URL":"http://localhost:8080"}}}};searxng_web_search(query, language="zh", engines="baidu,bing,sogou")聚合百度/搜狗/必应。Firecrawl 已接入时一般用不到。 - 避免:Perplexity(返回改写后的 LLM 答案,正是要躲的 bug)、Brave(2026-02 取消免费档、仅 snippet)。
待办(低优先,暂缓)
- 单段来源过多(5+)时右侧超宽,可加自动换行;manifest 改相对路径提升可移植性; 跨平台中文字体回退;URL 格式校验。这些不影响当前 macOS 单机使用。