Install any skill in seconds. Free to start, no credit card required.
Get Started Free →把多页 HTML 报告(主页 + N 子页 + PNG 资产)合并为单一独立 HTML / PDF / XLSX 三件交付物。 Use when finalizing a disease market sizing report and need shareable deliverables — single-file HTML for email/IM(零依赖,双击浏览器开),PDF for print/archive, XLSX for data export. 领域无关、跨疾病通用。 下游 of disease-market-sizing-html-template;上游 of email/IM/print 分发。 Use proactively whenever the user says "做交付物" / "生成 PDF" / "导 Excel" / "合并单文件 HTML"。
.claude/skills/ethanyoq-report-bundle-builder/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flash | 100% | 8 |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 41% | 0% |
| case-01 | ✗→✓ | ▲ Improved | -36% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -15% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 116% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 75% | 0% |
把市场调研项目的 main.html + page_*.html + flowchart_*.png 资产组装成 "双击即可阅读 / 邮件可发 / Excel 可分析"三件套。所有图片 base64 内嵌、PDF 自动 TOC、XLSX schema 驱动。
disease-market-sizing-html-templatedisease-market-sizing-orchestration 流水线article-writing 或 docx召回层 (7 retrieval skill: pubmed-eutils / europepmc-search / clinical-trials-v2 /
aact-bulk-trials / bioc-fulltext-fetch / pubtator-entity-search /
medical-evidence-grading)
↓
内容层 (disease-market-sizing-html-template + market-sizing-mece-foundation
+ decision-tree-with-lp-embedding + evidence-appendix-sync)
↓ 输出: main.html, page_*.html, flowchart_*.png
交付层 (本 skill: report-bundle-builder) ← YOU ARE HERE
↓ 输出: report_standalone.html, .pdf, _data.xlsx, delivery_manifest.json
分发层 (邮件 / IM / 网盘 / 打印)pythonfrom pathlib import Path from build_standalone_html import build_standalone_html result = build_standalone_html( main_html=Path("output/report.html"), sub_pages=[ ("AML", Path("output/page_AML.html")), ("MDS", Path("output/page_MDS.html")), ], png_dir=Path("output"), out_path=Path("output/report_standalone.html"), intra_page_anchors=["decision", "calc", "lp", "evidence"], # 血液项目用,通用场景留 None ) # {"ok": True, "size_mb": 9.3, "png_count": 8, # "missing_pngs": [], "validation": {...}}
关键参数:
sub_pages: [(slug, path), ...] · slug 用作 anchor id(#page-{slug})png_dir: PNG 资产目录(<img src="X.png"> 相对此目录解析)intra_page_anchors: 子页内部 href="#decision" 等改写为 href="#page-{slug}-decision" 的 anchor 名列表;None/空时不改写auto_validate: 默认 True · size > MIN_BUNDLE_BYTES(1 MB)+ anchor_count == 子页数pythonfrom build_pdf import build_pdf # 单遍模式(快) result = build_pdf( html_path=Path("output/report_standalone.html"), out_path=Path("output/report.pdf"), ) # 两遍模式(注入 TOC 页码) result = build_pdf( html_path=Path("output/report.html"), out_path=Path("output/report.pdf"), toc_anchors=[ ("#ch1", "一、执行摘要"), ("#ch2", "二、研究方法"), ], ) # {"ok": True, "size_mb": 9.6, "pages": 32, "mode": "two-pass"}
安全保证:
tempfile.mkstemp 避免并发碰撞pages=None,pages_ok=True(不算失败)详见 chrome-headless-setup.md。
pythonfrom build_xlsx import build_xlsx result = build_xlsx( data={ "overview": [["总 PMID", 200, "PubMed"], ["NCT", 30, "CT.gov"]], "lp_ranking": [["LP1", 8.5, "GRADE A"]], }, schema_yaml=Path("data/xlsx_schema.yaml"), out_path=Path("output/report_data.xlsx"), ) # {"ok": True, "sheets": ["T1_overview", "T2_lp_ranking"]}
Schema YAML 格式:
yamlsheets: - name: T1_overview headers: ["指标", "值", "来源"] column_widths: [22, 16, 28] data_key: overview - name: T2_lp_ranking headers: ["LP 名称", "得分", "证据等级"] column_widths: [30, 12, 18] data_key: lp_ranking
data dict 用 data_key 索引,值是 [[row...], [row...]]Sheet #2 missing 'headers')pythonfrom build_all import build_all_deliverables manifest = build_all_deliverables( report_dir=Path("output"), out_dir=Path("output"), sub_pages=None, # None → 自动扫描 page_*.html toc_anchors=[("#ch1","一、执行摘要")], # 给 PDF xlsx_schema=Path("data/xlsx_schema.yaml"), xlsx_data={...}, disease_slug="lung-tb-china", ) # 写入 output/delivery_manifest.json # 任一失败不阻塞其余,manifest 记录详情
自动检测顺序(main.html):
report_dir/main.htmlreport_dir/report.htmlreport_dir/report_v<N>.html 中版本号最大的(report_v25.html > report_v9.html)部分失败处理:
pdf.fallback_input=Trueok=False, error=skipped详见 bundle-manifest-spec.md。
| 函数 | 默认验证 | auto_validate=False 时 | |-----|---------|----------------------| | build_standalone_html | size > 1 MB,anchor_count == 子页数 | 跳过 | | build_pdf | size > 1 MB,pages ≥ 1(或 pypdf 不可用 → 不算失败) | 跳过 | | build_xlsx | size > 1 KB,sheet_count ≥ 1 | 跳过 |
阈值常量集中在 _validate.py:HTML_MIN_SIZE_BYTES / PDF_MIN_SIZE_BYTES / PDF_MIN_PAGES / XLSX_MIN_SIZE_BYTES / XLSX_MIN_SHEET_COUNT。
| 症状 | 原因 | 修复 | |------|------|------| | RuntimeError: No Chrome/Edge/Chromium found | PDF 渲染缺浏览器 | 装 Chrome 或显式 chrome_path= · 见 chrome-headless-setup.md | | ValueError: Main HTML missing <body> | 主页 HTML 不含 <body> | 检查 main_html 是否真页面 | | ValueError: Sheet #N missing 'X' | xlsx schema 字段缺失 | 补全 schema 的 name/headers/data_key | | KeyError: data['xxx'] missing | xlsx schema 引用 data_key 在 data dict 中缺失 | 对齐 schema.sheets].data_key 与 data 的 key | | HTML size < 1 MB,validation.size_ok=False | 内容太少或 PNG 丢失 | 检查 png_dir 是否对、PNG 文件存在 | | PDF pages 远低于预期 | HTML 渲染异常或 Chrome 崩溃 | 用 Chrome 手动开 HTML 看是否正常,看 Chrome stderr | | anchor_count < anchor_target | 子页 HTML 不含 main 标签或 slug 命名冲突 | 子页必须有 <main> 包裹主体内容 | | build_all 报 xlsx skipped | xlsx_schema 或 xlsx_data 任一为 None | 同时传两个,或接受 xlsx ok=False | | Output HTML 在浏览器打不开 | 主页编码非 UTF-8 | 重新生成主页保证 UTF-8 BOM 无 | | result["missing_pngs"] 非空 | 主页/子页引用了 png_dir 不存在的 PNG | 检查 PNG 文件名拼写或是否生成 | | 两遍模式 Pass 2 失败但 HTML 未损坏 | 备份恢复机制工作正常 | 排查 Chrome 错误,原 HTML 已自动从备份恢复 |
X | None 语法)httpx + lxml + pyyaml + openpyxl + 可选 pypdf(无则 PDF 不能测量页数,但仍能渲染)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | fail→pass | 14,907 | 5,290 | -65% | 1 | 1 | 0% | 2,663 | 3,748 | +41% | 0 | 0 | — |
case-05 | pass→pass | 15,708 | 7,967 | -49% | 1 | 1 | 0% | 3,038 | 4,126 | +36% | 0 | 0 | — |
case-06 | pass→pass | 15,882 | 13,386 | -16% | 1 | 1 | 0% | 2,880 | 5,346 | +86% | 0 | 0 | — |
case-01 | fail→pass | 27,398 | 6,942 | -75% | 1 | 1 | 0% | 6,221 | 3,988 | -36% | 0 | 0 | — |
case-02 | fail→pass | 24,593 | 10,273 | -58% | 1 | 1 | 0% | 5,291 | 4,490 | -15% | 0 | 0 | — |
case-03 | fail→fail | 5,159 | 5,662 | +10% | 1 | 1 | 0% | 432 | 2,860 | +562% | 0 | 0 | — |
case-04 | fail→fail | 24,369 | 20,444 | -16% | 1 | 1 | 0% | 5,452 | 7,036 | +29% | 0 | 0 | — |
case-08 | fail→pass | 10,128 | 5,729 | -43% | 1 | 1 | 0% | 1,724 | 3,725 | +116% | 0 | 0 | — |
case-09 | fail→pass | 9,732 | 4,592 | -53% | 1 | 1 | 0% | 2,002 | 3,511 | +75% | 0 | 0 | — |
case-10 | fail→pass | 8,732 | 2,986 | -66% | 1 | 1 | 0% | 1,594 | 3,194 | +100% | 0 | 0 | — |
case-11 | fail→pass | 10,113 | 5,782 | -43% | 1 | 1 | 0% | 2,368 | 3,878 | +64% | 0 | 0 | — |
case-12 | fail→pass | 5,878 | 3,068 | -48% | 1 | 1 | 0% | 1,132 | 3,307 | +192% | 0 | 0 | — |
case-13 | fail→pass | 7,358 | 2,930 | -60% | 1 | 1 | 0% | 1,353 | 3,211 | +137% | 0 | 0 | — |
case-14 | pass→pass | 4,046 | 3,112 | -23% | 1 | 1 | 0% | 931 | 3,221 | +246% | 0 | 0 | — |
case-15 | fail→pass | 10,756 | 2,685 | -75% | 1 | 1 | 0% | 2,200 | 3,199 | +45% | 0 | 0 | — |
case-16 | fail→pass | 11,470 | 5,495 | -52% | 1 | 1 | 0% | 1,761 | 3,748 | +113% | 0 | 0 | — |
case-17 | fail→pass | 8,651 | 2,445 | -72% | 1 | 1 | 0% | 1,786 | 3,085 | +73% | 0 | 0 | — |
case-18 | fail→pass | 7,608 | 1,878 | -75% | 1 | 1 | 0% | 1,441 | 2,896 | +101% | 0 | 0 | — |
case-19 | fail→pass | 9,927 | 2,333 | -76% | 1 | 1 | 0% | 1,972 | 3,002 | +52% | 0 | 0 | — |
case-20 | fail→pass | 12,171 | 3,185 | -74% | 1 | 1 | 0% | 2,469 | 3,293 | +33% | 0 | 0 | — |
case-21 | pass→pass | 7,270 | 2,194 | -70% | 1 | 1 | 0% | 1,462 | 3,042 | +108% | 0 | 0 | — |
case-22 | fail→pass | 9,995 | 7,539 | -25% | 1 | 1 | 0% | 2,110 | 4,174 | +98% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +73 percentage points is the difference between those two pass rates over the 21 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.