[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M712ZNRGJM34KX1STKMPD1T4":3,"skill-same:01M712ZNRGJM34KX1STKMPD1T4":74},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":51,"commitSha":52,"skillDirectory":53,"forksCount":54,"repositoryHtmlUrl":55,"repositoryHomepageUrl":6,"ownerHtmlUrl":56,"ownerBlogUrl":57,"ownerDescription":58,"updatedAt":59,"install":60,"resources":73},"01M712ZNRGJM34KX1STKMPD1T4","bioc-fulltext-fetch",null,"给定 PMID \u002F PMCID 取结构化全文（BioC XML\u002FJSON）。封装 NLM BioC PubMed API（标题+摘要）和 BioC PMC OA API（开放获取全文），输出按章节切分的段落、表格上下文、RAG 友好 chunk。适合大批量入向量库。触发词：全文抽取 \u002F BioC \u002F RAG 入库 \u002F 文献全文 \u002F PMC 开放获取 \u002F structured fulltext \u002F 段落抽取 \u002F 表格抽取 \u002F fulltext to vector store。","EthanYoQ\u002FSkill-hub","EthanYoQ","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F257484394?v=4","01M70C9QKWR1YS0S7V26DNB18H","Ethan","01M70C9QMER047R5ANZZA6KC2P",11,"MIT","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Farchives\u002Fuser-installed-skills\u002Fbioc-fulltext-fetch\u002Fc1d806facb0db73a\u002Fbioc-fulltext-fetch\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"bioc fulltext fetch","fetch","bioc","section-segmentation","vector-store","table-extraction","paragraph-extraction","structured-fulltext","rag-ingestion","pmc-open-access","fulltext-extraction","chunk","pmc","pubmed","ingestion","access","open","api","segmentation","vector","2026-11-10 10:12:30",false,"---\nname: bioc-fulltext-fetch\ndescription: 给定 PMID \u002F PMCID 取结构化全文（BioC XML\u002FJSON）。封装 NLM BioC PubMed API（标题+摘要）和 BioC PMC OA API（开放获取全文），输出按章节切分的段落、表格上下文、RAG 友好 chunk。适合大批量入向量库。触发词：全文抽取 \u002F BioC \u002F RAG 入库 \u002F 文献全文 \u002F PMC 开放获取 \u002F structured fulltext \u002F 段落抽取 \u002F 表格抽取 \u002F fulltext to vector store。\nkeywords:\n  - bioc\n  - fulltext-extraction\n  - pmc-open-access\n  - rag-ingestion\n  - structured-fulltext\n  - paragraph-extraction\n  - table-extraction\n  - vector-store\n  - chunking\n  - section-segmentation\nlicense: MIT\n---\n\n# BioC Fulltext Fetch\n\n封装 NLM BioC API,用于将 PubMed \u002F PMC 文献转换为 RAG 友好的结构化全文 chunk。\n\n## 1. 职责边界（单一职责）\n\n**只做**:给定 PMID \u002F PMCID,取结构化全文(BioC XML\u002FJSON 格式),输出段落、章节、表格上下文、RAG chunk。\n\n**不做**:\n- 检索文献(交给 `pubmed-eutils` \u002F `europepmc-search`)\n- 实体标注(交给 `pubtator3`)\n- 向量化(交给下游的 embedding pipeline \u002F LangChain \u002F LlamaIndex)\n- ID 转换(可调用 `pubmed-eutils.elink_pubmed_to_pmc`,但本 skill 不实现 elink)\n\n如果需要这些能力,请使用对应的 skill。\n\n## 2. 何时使用\n\n触发场景:\n- \"把这批 PMID 的全文取下来,准备入 ChromaDB\"\n- \"我有 PMCID 列表,需要按章节切分做 RAG\"\n- \"提取 Methods 章节段落\"\n- \"找出表格周围的语义文本\"\n- \"Build vector index from PMC open access papers\"\n\n不要使用本 skill 当:\n- 用户只需要标题\u002F摘要的纯文本 → 用 `pubmed-eutils.efetch`\n- 用户需要做语义检索 → 用 `europepmc-search` 或 `pubmed-search`\n- 用户需要实体标注(基因\u002F疾病\u002F化合物) → 用 PubTator3 类 skill\n\n## 3. 两个 BioC 端点\n\n| 端点 | 输入 | 覆盖 | 内容 |\n|------|------|------|------|\n| **BioC PubMed** | PMID | 全 PubMed (~37M) | 标题 + 摘要 + MeSH (结构化) |\n| **BioC PMC OA** | PMCID | PMC 开放获取子集 (~10M) | 全文(章节、段落、表格、图注) |\n\n端点示例:\n```\n# 摘要级\nhttps:\u002F\u002Fwww.ncbi.nlm.nih.gov\u002Fresearch\u002Fbionlp\u002FRESTful\u002Fpubmed.cgi\u002FBioC_json\u002F{PMID}\u002Funicode\n\n# 全文级\nhttps:\u002F\u002Fwww.ncbi.nlm.nih.gov\u002Fresearch\u002Fbionlp\u002FRESTful\u002Fpmcoa.cgi\u002FBioC_json\u002F{PMCID}\u002Funicode\n```\n\n文档参考:\n- BioC PubMed: https:\u002F\u002Fwww.ncbi.nlm.nih.gov\u002Fresearch\u002Fbionlp\u002FAPIs\u002FBioC-PubMed\u002F\n- BioC PMC OA: https:\u002F\u002Fwww.ncbi.nlm.nih.gov\u002Fresearch\u002Fbionlp\u002FAPIs\u002FBioC-PMC\u002F\n\n## 4. 推荐工作流\n\n```\n用户给 PMID list\n  │\n  ▼\n[可选] elink PMID → PMCID  (调用 pubmed-eutils)\n  │\n  ├── PMCID 存在 + 是 OA ──► fetch_fulltext_bioc(pmcid)  ← 全文\n  │                                  │\n  │                                  ▼\n  │                          extract_paragraphs \u002F extract_tables_context\n  │                                  │\n  │                                  ▼\n  │                          to_rag_chunks(chunk_size=512, overlap=50)\n  │\n  └── 不可用 \u002F 非 OA ──────► fetch_abstract_bioc(pmid)   ← 摘要兜底\n                                     │\n                                     ▼\n                              to_rag_chunks (摘要也按段切)\n```\n\n## 5. 核心能力(5 个函数)\n\n### 5.1 `fetch_abstract_bioc(pmid: str) → BioCDocument`\n\n取标题 + 摘要 + MeSH 的 BioC 结构化版本。\n\n```python\ndoc = fetch_abstract_bioc(\"39523456\")\n# doc.passages → [{\"infons\": {\"section_type\": \"TITLE\"}, \"text\": \"...\"},\n#                 {\"infons\": {\"section_type\": \"ABSTRACT\"}, \"text\": \"...\"}]\n# doc.infons[\"mesh\"] → [\"Leukemia, Myeloid, Acute\", \"FLT3 Mutation\", ...]\n```\n\n### 5.2 `fetch_fulltext_bioc(pmcid: str) → BioCDocument`\n\n取 PMC OA 全文,按章节切分(`section_type` ∈ {TITLE, ABSTRACT, INTRO, METHODS, RESULTS, DISCUSS, CONCL, REF, FIG, TABLE, ...})。\n\n```python\ndoc = fetch_fulltext_bioc(\"PMC10234567\")\n# 不可用时抛 NotOpenAccessError 或 NotFoundError\n```\n\n错误处理:\n- `404` → 该 PMCID 不在 PMC OA 子集 → 抛 `NotOpenAccessError`,调用方应回退到 `fetch_abstract_bioc`\n- `429` → 限流 → 退避重试(最多 3 次,指数退避)\n- 网络错误 → 抛 `BioCFetchError`,记录日志\n\n### 5.3 `extract_paragraphs(doc: BioCDocument) → list[Paragraph]`\n\n按 BioC `passage` 提取段落数组,保留 section 标签 + 字符 offset。\n\n```python\nparagraphs = extract_paragraphs(doc)\n# [\n#   {\"section_label\": \"INTRO\", \"text\": \"Acute myeloid leukemia (AML) is...\",\n#    \"offset_start\": 1234, \"offset_end\": 1820},\n#   {\"section_label\": \"METHODS\", \"text\": \"Flow cytometry was performed...\",\n#    \"offset_start\": 2890, \"offset_end\": 3401},\n#   ...\n# ]\n```\n\n实现要点:\n- 跳过 `section_type ∈ {REF, FIG_LABEL}` 的纯标签段\n- 合并连续相同 section 的短 passage(\u003C 50 字符)\n- offset 来自 BioC `passage.offset` 字段,保持原文索引一致\n\n### 5.4 `extract_tables_context(doc: BioCDocument, window: int = 200) → list[TableContext]`\n\n提取每个表格的上下文(前后 200 字),帮助 RAG 给表格打语义标签。\n\n```python\ntables = extract_tables_context(doc, window=200)\n# [\n#   {\n#     \"table_id\": \"T1\",\n#     \"caption\": \"Patient baseline characteristics\",\n#     \"before_context\": \"...as shown in Table 1, baseline characteristics...\",\n#     \"after_context\": \"These data confirm that age and ELN risk...\",\n#     \"section\": \"RESULTS\"\n#   },\n#   ...\n# ]\n```\n\n实现要点:\n- BioC 中 table 通常以 `section_type=TABLE` 或 `passage.infons[\"xml\"]=\"table\"` 出现\n- 上下文从同 section 内的相邻 passage 拼接\n- 表格本体的结构化数据不解析(交给专门的 table parser),只取 caption + 周围文本\n\n### 5.5 `to_rag_chunks(doc: BioCDocument, chunk_size: int = 512, overlap: int = 50) → list[Chunk]`\n\n把 BioC 文档切成 RAG 入库友好的 chunk。\n\n```python\nchunks = to_rag_chunks(doc, chunk_size=512, overlap=50)\n# 每个 chunk:\n{\n    \"chunk_id\": \"PMC10234567_METHODS_2890\",\n    \"text\": \"Flow cytometry was performed on bone marrow aspirates...\",\n    \"metadata\": {\n        \"pmcid\": \"PMC10234567\",\n        \"pmid\": \"39523456\",\n        \"section\": \"METHODS\",\n        \"offset_start\": 2890,\n        \"offset_end\": 3401,\n        \"doi\": \"10.1038\u002Fs41586-024-xxxxx\",\n        \"year\": 2024,\n        \"is_oa\": True,\n        \"title\": \"Targeting FLT3 in AML: ...\",\n        \"journal\": \"Nature\",\n    }\n}\n```\n\n切片策略:\n- 单位:tokens(用 `tiktoken` cl100k_base)而非字符,默认 512 token\n- **不跨章节切**:每个 section 独立切片,避免 chunk 跨越 Methods\u002FResults 边界\n- 段落优先:尽量在段落边界切,实在超长再硬切\n- overlap:50 token,保证语义连续性\n- `chunk_id` 格式:`{pmcid}_{section}_{offset_start}` 唯一可追溯\n\n## 6. 速率限制 + 缓存\n\n### 速率\n- BioC 不强制 RPS,但服务端对滥用 IP 会封禁\n- 建议 ≤ 5 RPS,批量超过 100 篇时降到 3 RPS\n- 用 `asyncio.Semaphore(5)` 或 `aiolimiter` 控制并发\n\n### 缓存\n- 位置:`~\u002F.cache\u002Fbioc\u002F{pmcid_or_pmid}.json`(原始 BioC JSON)\n- 索引:SQLite 元数据表 `~\u002F.cache\u002Fbioc\u002Findex.db`\n  ```sql\n  CREATE TABLE bioc_cache (\n      key TEXT PRIMARY KEY,        -- \"pmc:PMC10234567\" or \"pmid:39523456\"\n      file_path TEXT,\n      fetched_at INTEGER,           -- Unix epoch\n      is_oa INTEGER,\n      doi TEXT,\n      year INTEGER\n  );\n  ```\n- 过期:默认 30 天(`CACHE_TTL_DAYS=30`),可通过环境变量覆盖\n- 命中流程:查 SQLite → 检查 mtime → 未过期则读 JSON;过期或不存在则重新拉取\n- 失效场景:用户传 `force_refresh=True` 时绕过缓存\n\n## 7. 错误处理\n\n| 错误 | 类型 | 处理 |\n|------|------|------|\n| HTTP 404 (PMC OA) | `NotOpenAccessError` | 调用方回退到 abstract |\n| HTTP 404 (PubMed) | `NotFoundError` | 该 PMID 不存在,记录并跳过 |\n| HTTP 429 | `RateLimitError` | 指数退避重试 (1s, 2s, 4s),最多 3 次 |\n| HTTP 5xx | `BioCServerError` | 重试 3 次后抛出 |\n| JSON 解析失败 | `BioCParseError` | 记录原始响应,跳过 |\n| 网络超时 | `BioCTimeoutError` | timeout=30s,重试 1 次 |\n\n所有错误继承 `BioCFetchError` 基类,调用方可以统一捕获。\n\n## 8. RAG 集成示例\n\n### 8.1 LangChain\n\n```python\nfrom langchain.schema import Document\nfrom langchain_community.vectorstores import Chroma\nfrom langchain_openai import OpenAIEmbeddings\n\n# 1. 取全文 → chunk\npmcids = [\"PMC10234567\", \"PMC11122233\", ...]\nall_chunks = []\nfor pmcid in pmcids:\n    try:\n        doc = fetch_fulltext_bioc(pmcid)\n    except NotOpenAccessError:\n        # 回退到摘要\n        pmid = elink_pmc_to_pubmed(pmcid)  # 调用 pubmed-eutils\n        doc = fetch_abstract_bioc(pmid)\n    all_chunks.extend(to_rag_chunks(doc, chunk_size=512, overlap=50))\n\n# 2. 转 LangChain Document\nlc_docs = [\n    Document(page_content=c[\"text\"], metadata=c[\"metadata\"])\n    for c in all_chunks\n]\n\n# 3. 入向量库\nvectorstore = Chroma.from_documents(\n    lc_docs,\n    embedding=OpenAIEmbeddings(model=\"text-embedding-3-small\"),\n    collection_name=\"aml_literature\",\n    persist_directory=\".\u002Fchroma_aml\",\n)\n```\n\n### 8.2 LlamaIndex\n\n```python\nfrom llama_index.core import Document, VectorStoreIndex\nfrom llama_index.embeddings.openai import OpenAIEmbedding\n\nli_docs = [\n    Document(\n        text=c[\"text\"],\n        metadata=c[\"metadata\"],\n        doc_id=c[\"chunk_id\"],\n        excluded_llm_metadata_keys=[\"offset_start\", \"offset_end\"],\n    )\n    for c in all_chunks\n]\nindex = VectorStoreIndex.from_documents(\n    li_docs,\n    embed_model=OpenAIEmbedding(model=\"text-embedding-3-small\"),\n)\nindex.storage_context.persist(\".\u002Fli_aml_index\")\n```\n\n### 8.3 元数据过滤检索(LangChain)\n\n```python\n# 只在 Methods 章节检索\nretriever = vectorstore.as_retriever(\n    search_kwargs={\"filter\": {\"section\": \"METHODS\"}, \"k\": 5}\n)\nresults = retriever.invoke(\"flow cytometry gating strategy for AML blasts\")\n```\n\n## 9. 与其他 skill 的协作\n\n| 配合的 skill | 用途 |\n|--------------|------|\n| `pubmed-eutils` | PMID ↔ PMCID 转换(elink) |\n| `europepmc-search` \u002F `pubmed-search` | 先检索得到 PMID list |\n| `pubtator3` | 在 chunk 上叠加实体标注(基因、疾病、化合物) |\n| `medical-research` | 上层科研 narrative 生成 |\n\n典型 pipeline:\n```\neuropepmc-search ──► PMID list ──► pubmed-eutils.elink ──► PMCID list\n                                                              │\n                                                              ▼\n                                            bioc-fulltext-fetch (本 skill)\n                                                              │\n                                                              ▼\n                                          [可选] pubtator3 实体叠加\n                                                              │\n                                                              ▼\n                                                     LangChain \u002F LlamaIndex\n```\n\n## 10. 环境与依赖\n\n```toml\n# pyproject 片段\n[project]\ndependencies = [\n    \"httpx>=0.27\",          # 异步 HTTP\n    \"aiolimiter>=1.1\",      # RPS 限流\n    \"tiktoken>=0.7\",        # token 计数\n    \"tenacity>=9.0\",        # 退避重试\n    \"platformdirs>=4.0\",    # 缓存目录定位\n]\n```\n\n环境变量:\n- `BIOC_CACHE_DIR`(可选,默认 `~\u002F.cache\u002Fbioc`)\n- `BIOC_CACHE_TTL_DAYS`(默认 30)\n- `BIOC_MAX_RPS`(默认 5)\n- `BIOC_TIMEOUT_SEC`(默认 30)\n\n## 11. 测试要点\n\n| 测试 | 预期 |\n|------|------|\n| `fetch_abstract_bioc(\"39523456\")` | 返回 title + abstract passages |\n| `fetch_fulltext_bioc(\"PMC10234567\")` | 返回章节切分的全文 |\n| `fetch_fulltext_bioc(\"PMC1\")` (非 OA) | 抛 `NotOpenAccessError` |\n| `extract_paragraphs(doc)` | 段落数 ≥ 3,每段有 section_label |\n| `extract_tables_context(doc)` | 表格上下文左右各 ≤ 200 字 |\n| `to_rag_chunks(doc, 512, 50)` | chunk token ≤ 512,overlap ≈ 50,不跨章节 |\n| 缓存命中 | 第二次调用 \u003C 50ms,无网络请求 |\n| 429 退避 | 模拟 429 响应后能在 3 次内成功 |\n\n最低单元测试覆盖率 80%,集成测试用 5 篇真实 PMCID 跑端到端。\n\n## 12. 已知限制与避坑\n\n1. **OA 子集只占 PMC 的 ~25%**:大量 PMID 没有可用全文,务必准备 abstract 兜底路径\n2. **章节标签不统一**:不同期刊的 `section_type` 命名有差异(`INTRO` vs `Introduction`),做归一化\n3. **公式与符号**:BioC unicode 端点把数学符号转 Unicode,但希腊字母\u002F特殊符号偶尔丢失,关键公式建议从原 XML 端点取\n4. **图与表的图像**:BioC 不返回图像,只有 caption 和周围文本;如需图像走 PMC FTP\n5. **大文档内存**:单篇 PMC 全文 BioC JSON 可达 1-3 MB,批量处理时注意流式或分批\n6. **非英文文献**:BioC PubMed 对非英文摘要支持有限,可能返回原文(西班牙语\u002F中文等),embedding 模型需多语言版本\n7. **历史版本**:BioC 端点不返回文章版本号,如需追溯版本变更走 NCBI E-utilities 的 history\n8. **不要直接走 PubTator3 全文端点替代**:PubTator3 的 BioC 输出是带实体标注的\"加料版\",数据量更大且会绑定标注模型版本,本 skill 保持纯净结构化全文,标注由独立 skill 叠加\n\n完成。该 skill 应当与 `pubmed-eutils` 和 `europepmc-search` 形成\"检索 → ID 转换 → 全文抽取 → RAG 入库\"完整闭环。\n","# BioC Fulltext Fetch\n\n封装 NLM BioC API,用于将 PubMed \u002F PMC 文献转换为 RAG 友好的结构化全文 chunk。\n\n## 1. 职责边界（单一职责）\n\n**只做**:给定 PMID \u002F PMCID,取结构化全文(BioC XML\u002FJSON 格式),输出段落、章节、表格上下文、RAG chunk。\n\n**不做**:\n- 检索文献(交给 `pubmed-eutils` \u002F `europepmc-search`)\n- 实体标注(交给 `pubtator3`)\n- 向量化(交给下游的 embedding pipeline \u002F LangChain \u002F LlamaIndex)\n- ID 转换(可调用 `pubmed-eutils.elink_pubmed_to_pmc`,但本 skill 不实现 elink)\n\n如果需要这些能力,请使用对应的 skill。\n\n## 2. 何时使用\n\n触发场景:\n- \"把这批 PMID 的全文取下来,准备入 ChromaDB\"\n- \"我有 PMCID 列表,需要按章节切分做 RAG\"\n- \"提取 Methods 章节段落\"\n- \"找出表格周围的语义文本\"\n- \"Build vector index from PMC open access papers\"\n\n不要使用本 skill 当:\n- 用户只需要标题\u002F摘要的纯文本 → 用 `pubmed-eutils.efetch`\n- 用户需要做语义检索 → 用 `europepmc-search` 或 `pubmed-search`\n- 用户需要实体标注(基因\u002F疾病\u002F化合物) → 用 PubTator3 类 skill\n\n## 3. 两个 BioC 端点\n\n| 端点 | 输入 | 覆盖 | 内容 |\n|------|------|------|------|\n| **BioC PubMed** | PMID | 全 PubMed (~37M) | 标题 + 摘要 + MeSH (结构化) |\n| **BioC PMC OA** | PMCID | PMC 开放获取子集 (~10M) | 全文(章节、段落、表格、图注) |\n\n端点示例:\n```\n# 摘要级\nhttps:\u002F\u002Fwww.ncbi.nlm.nih.gov\u002Fresearch\u002Fbionlp\u002FRESTful\u002Fpubmed.cgi\u002FBioC_json\u002F{PMID}\u002Funicode\n\n# 全文级\nhttps:\u002F\u002Fwww.ncbi.nlm.nih.gov\u002Fresearch\u002Fbionlp\u002FRESTful\u002Fpmcoa.cgi\u002FBioC_json\u002F{PMCID}\u002Funicode\n```\n\n文档参考:\n- BioC PubMed: https:\u002F\u002Fwww.ncbi.nlm.nih.gov\u002Fresearch\u002Fbionlp\u002FAPIs\u002FBioC-PubMed\u002F\n- BioC PMC OA: https:\u002F\u002Fwww.ncbi.nlm.nih.gov\u002Fresearch\u002Fbionlp\u002FAPIs\u002FBioC-PMC\u002F\n\n## 4. 推荐工作流\n\n```\n用户给 PMID list\n  │\n  ▼\n[可选] elink PMID → PMCID  (调用 pubmed-eutils)\n  │\n  ├── PMCID 存在 + 是 OA ──► fetch_fulltext_bioc(pmcid)  ← 全文\n  │                                  │\n  │                                  ▼\n  │                          extract_paragraphs \u002F extract_tables_context\n  │                                  │\n  │                                  ▼\n  │                          to_rag_chunks(chunk_size=512, overlap=50)\n  │\n  └── 不可用 \u002F 非 OA ──────► fetch_abstract_bioc(pmid)   ← 摘要兜底\n                                     │\n                                     ▼\n                              to_rag_chunks (摘要也按段切)\n```\n\n## 5. 核心能力(5 个函数)\n\n### 5.1 `fetch_abstract_bioc(pmid: str) → BioCDocument`\n\n取标题 + 摘要 + MeSH 的 BioC 结构化版本。\n\n```python\ndoc = fetch_abstract_bioc(\"39523456\")\n# doc.passages → [{\"infons\": {\"section_type\": \"TITLE\"}, \"text\": \"...\"},\n#                 {\"infons\": {\"section_type\": \"ABSTRACT\"}, \"text\": \"...\"}]\n# doc.infons[\"mesh\"] → [\"Leukemia, Myeloid, Acute\", \"FLT3 Mutation\", ...]\n```\n\n### 5.2 `fetch_fulltext_bioc(pmcid: str) → BioCDocument`\n\n取 PMC OA 全文,按章节切分(`section_type` ∈ {TITLE, ABSTRACT, INTRO, METHODS, RESULTS, DISCUSS, CONCL, REF, FIG, TABLE, ...})。\n\n```python\ndoc = fetch_fulltext_bioc(\"PMC10234567\")\n# 不可用时抛 NotOpenAccessError 或 NotFoundError\n```\n\n错误处理:\n- `404` → 该 PMCID 不在 PMC OA 子集 → 抛 `NotOpenAccessError`,调用方应回退到 `fetch_abstract_bioc`\n- `429` → 限流 → 退避重试(最多 3 次,指数退避)\n- 网络错误 → 抛 `BioCFetchError`,记录日志\n\n### 5.3 `extract_paragraphs(doc: BioCDocument) → list[Paragraph]`\n\n按 BioC `passage` 提取段落数组,保留 section 标签 + 字符 offset。\n\n```python\nparagraphs = extract_paragraphs(doc)\n# [\n#   {\"section_label\": \"INTRO\", \"text\": \"Acute myeloid leukemia (AML) is...\",\n#    \"offset_start\": 1234, \"offset_end\": 1820},\n#   {\"section_label\": \"METHODS\", \"text\": \"Flow cytometry was performed...\",\n#    \"offset_start\": 2890, \"offset_end\": 3401},\n#   ...\n# ]\n```\n\n实现要点:\n- 跳过 `section_type ∈ {REF, FIG_LABEL}` 的纯标签段\n- 合并连续相同 section 的短 passage(\u003C 50 字符)\n- offset 来自 BioC `passage.offset` 字段,保持原文索引一致\n\n### 5.4 `extract_tables_context(doc: BioCDocument, window: int = 200) → list[TableContext]`\n\n提取每个表格的上下文(前后 200 字),帮助 RAG 给表格打语义标签。\n\n```python\ntables = extract_tables_context(doc, window=200)\n# [\n#   {\n#     \"table_id\": \"T1\",\n#     \"caption\": \"Patient baseline characteristics\",\n#     \"before_context\": \"...as shown in Table 1, baseline characteristics...\",\n#     \"after_context\": \"These data confirm that age and ELN risk...\",\n#     \"section\": \"RESULTS\"\n#   },\n#   ...\n# ]\n```\n\n实现要点:\n- BioC 中 table 通常以 `section_type=TABLE` 或 `passage.infons[\"xml\"]=\"table\"` 出现\n- 上下文从同 section 内的相邻 passage 拼接\n- 表格本体的结构化数据不解析(交给专门的 table parser),只取 caption + 周围文本\n\n### 5.5 `to_rag_chunks(doc: BioCDocument, chunk_size: int = 512, overlap: int = 50) → list[Chunk]`\n\n把 BioC 文档切成 RAG 入库友好的 chunk。\n\n```python\nchunks = to_rag_chunks(doc, chunk_size=512, overlap=50)\n# 每个 chunk:\n{\n    \"chunk_id\": \"PMC10234567_METHODS_2890\",\n    \"text\": \"Flow cytometry was performed on bone marrow aspirates...\",\n    \"metadata\": {\n        \"pmcid\": \"PMC10234567\",\n        \"pmid\": \"39523456\",\n        \"section\": \"METHODS\",\n        \"offset_start\": 2890,\n        \"offset_end\": 3401,\n        \"doi\": \"10.1038\u002Fs41586-024-xxxxx\",\n        \"year\": 2024,\n        \"is_oa\": True,\n        \"title\": \"Targeting FLT3 in AML: ...\",\n        \"journal\": \"Nature\",\n    }\n}\n```\n\n切片策略:\n- 单位:tokens(用 `tiktoken` cl100k_base)而非字符,默认 512 token\n- **不跨章节切**:每个 section 独立切片,避免 chunk 跨越 Methods\u002FResults 边界\n- 段落优先:尽量在段落边界切,实在超长再硬切\n- overlap:50 token,保证语义连续性\n- `chunk_id` 格式:`{pmcid}_{section}_{offset_start}` 唯一可追溯\n\n## 6. 速率限制 + 缓存\n\n### 速率\n- BioC 不强制 RPS,但服务端对滥用 IP 会封禁\n- 建议 ≤ 5 RPS,批量超过 100 篇时降到 3 RPS\n- 用 `asyncio.Semaphore(5)` 或 `aiolimiter` 控制并发\n\n### 缓存\n- 位置:`~\u002F.cache\u002Fbioc\u002F{pmcid_or_pmid}.json`(原始 BioC JSON)\n- 索引:SQLite 元数据表 `~\u002F.cache\u002Fbioc\u002Findex.db`\n  ```sql\n  CREATE TABLE bioc_cache (\n      key TEXT PRIMARY KEY,        -- \"pmc:PMC10234567\" or \"pmid:39523456\"\n      file_path TEXT,\n      fetched_at INTEGER,           -- Unix epoch\n      is_oa INTEGER,\n      doi TEXT,\n      year INTEGER\n  );\n  ```\n- 过期:默认 30 天(`CACHE_TTL_DAYS=30`),可通过环境变量覆盖\n- 命中流程:查 SQLite → 检查 mtime → 未过期则读 JSON;过期或不存在则重新拉取\n- 失效场景:用户传 `force_refresh=True` 时绕过缓存\n\n## 7. 错误处理\n\n| 错误 | 类型 | 处理 |\n|------|------|------|\n| HTTP 404 (PMC OA) | `NotOpenAccessError` | 调用方回退到 abstract |\n| HTTP 404 (PubMed) | `NotFoundError` | 该 PMID 不存在,记录并跳过 |\n| HTTP 429 | `RateLimitError` | 指数退避重试 (1s, 2s, 4s),最多 3 次 |\n| HTTP 5xx | `BioCServerError` | 重试 3 次后抛出 |\n| JSON 解析失败 | `BioCParseError` | 记录原始响应,跳过 |\n| 网络超时 | `BioCTimeoutError` | timeout=30s,重试 1 次 |\n\n所有错误继承 `BioCFetchError` 基类,调用方可以统一捕获。\n\n## 8. RAG 集成示例\n\n### 8.1 LangChain\n\n```python\nfrom langchain.schema import Document\nfrom langchain_community.vectorstores import Chroma\nfrom langchain_openai import OpenAIEmbeddings\n\n# 1. 取全文 → chunk\npmcids = [\"PMC10234567\", \"PMC11122233\", ...]\nall_chunks = []\nfor pmcid in pmcids:\n    try:\n        doc = fetch_fulltext_bioc(pmcid)\n    except NotOpenAccessError:\n        # 回退到摘要\n        pmid = elink_pmc_to_pubmed(pmcid)  # 调用 pubmed-eutils\n        doc = fetch_abstract_bioc(pmid)\n    all_chunks.extend(to_rag_chunks(doc, chunk_size=512, overlap=50))\n\n# 2. 转 LangChain Document\nlc_docs = [\n    Document(page_content=c[\"text\"], metadata=c[\"metadata\"])\n    for c in all_chunks\n]\n\n# 3. 入向量库\nvectorstore = Chroma.from_documents(\n    lc_docs,\n    embedding=OpenAIEmbeddings(model=\"text-embedding-3-small\"),\n    collection_name=\"aml_literature\",\n    persist_directory=\".\u002Fchroma_aml\",\n)\n```\n\n### 8.2 LlamaIndex\n\n```python\nfrom llama_index.core import Document, VectorStoreIndex\nfrom llama_index.embeddings.openai import OpenAIEmbedding\n\nli_docs = [\n    Document(\n        text=c[\"text\"],\n        metadata=c[\"metadata\"],\n        doc_id=c[\"chunk_id\"],\n        excluded_llm_metadata_keys=[\"offset_start\", \"offset_end\"],\n    )\n    for c in all_chunks\n]\nindex = VectorStoreIndex.from_documents(\n    li_docs,\n    embed_model=OpenAIEmbedding(model=\"text-embedding-3-small\"),\n)\nindex.storage_context.persist(\".\u002Fli_aml_index\")\n```\n\n### 8.3 元数据过滤检索(LangChain)\n\n```python\n# 只在 Methods 章节检索\nretriever = vectorstore.as_retriever(\n    search_kwargs={\"filter\": {\"section\": \"METHODS\"}, \"k\": 5}\n)\nresults = retriever.invoke(\"flow cytometry gating strategy for AML blasts\")\n```\n\n## 9. 与其他 skill 的协作\n\n| 配合的 skill | 用途 |\n|--------------|------|\n| `pubmed-eutils` | PMID ↔ PMCID 转换(elink) |\n| `europepmc-search` \u002F `pubmed-search` | 先检索得到 PMID list |\n| `pubtator3` | 在 chunk 上叠加实体标注(基因、疾病、化合物) |\n| `medical-research` | 上层科研 narrative 生成 |\n\n典型 pipeline:\n```\neuropepmc-search ──► PMID list ──► pubmed-eutils.elink ──► PMCID list\n                                                              │\n                                                              ▼\n                                            bioc-fulltext-fetch (本 skill)\n                                                              │\n                                                              ▼\n                                          [可选] pubtator3 实体叠加\n                                                              │\n                                                              ▼\n                                                     LangChain \u002F LlamaIndex\n```\n\n## 10. 环境与依赖\n\n```toml\n# pyproject 片段\n[project]\ndependencies = [\n    \"httpx>=0.27\",          # 异步 HTTP\n    \"aiolimiter>=1.1\",      # RPS 限流\n    \"tiktoken>=0.7\",        # token 计数\n    \"tenacity>=9.0\",        # 退避重试\n    \"platformdirs>=4.0\",    # 缓存目录定位\n]\n```\n\n环境变量:\n- `BIOC_CACHE_DIR`(可选,默认 `~\u002F.cache\u002Fbioc`)\n- `BIOC_CACHE_TTL_DAYS`(默认 30)\n- `BIOC_MAX_RPS`(默认 5)\n- `BIOC_TIMEOUT_SEC`(默认 30)\n\n## 11. 测试要点\n\n| 测试 | 预期 |\n|------|------|\n| `fetch_abstract_bioc(\"39523456\")` | 返回 title + abstract passages |\n| `fetch_fulltext_bioc(\"PMC10234567\")` | 返回章节切分的全文 |\n| `fetch_fulltext_bioc(\"PMC1\")` (非 OA) | 抛 `NotOpenAccessError` |\n| `extract_paragraphs(doc)` | 段落数 ≥ 3,每段有 section_label |\n| `extract_tables_context(doc)` | 表格上下文左右各 ≤ 200 字 |\n| `to_rag_chunks(doc, 512, 50)` | chunk token ≤ 512,overlap ≈ 50,不跨章节 |\n| 缓存命中 | 第二次调用 \u003C 50ms,无网络请求 |\n| 429 退避 | 模拟 429 响应后能在 3 次内成功 |\n\n最低单元测试覆盖率 80%,集成测试用 5 篇真实 PMCID 跑端到端。\n\n## 12. 已知限制与避坑\n\n1. **OA 子集只占 PMC 的 ~25%**:大量 PMID 没有可用全文,务必准备 abstract 兜底路径\n2. **章节标签不统一**:不同期刊的 `section_type` 命名有差异(`INTRO` vs `Introduction`),做归一化\n3. **公式与符号**:BioC unicode 端点把数学符号转 Unicode,但希腊字母\u002F特殊符号偶尔丢失,关键公式建议从原 XML 端点取\n4. **图与表的图像**:BioC 不返回图像,只有 caption 和周围文本;如需图像走 PMC FTP\n5. **大文档内存**:单篇 PMC 全文 BioC JSON 可达 1-3 MB,批量处理时注意流式或分批\n6. **非英文文献**:BioC PubMed 对非英文摘要支持有限,可能返回原文(西班牙语\u002F中文等),embedding 模型需多语言版本\n7. **历史版本**:BioC 端点不返回文章版本号,如需追溯版本变更走 NCBI E-utilities 的 history\n8. **不要直接走 PubTator3 全文端点替代**:PubTator3 的 BioC 输出是带实体标注的\"加料版\",数据量更大且会绑定标注模型版本,本 skill 保持纯净结构化全文,标注由独立 skill 叠加\n\n完成。该 skill 应当与 `pubmed-eutils` 和 `europepmc-search` 形成\"检索 → ID 转换 → 全文抽取 → RAG 入库\"完整闭环。\n",{"name":5,"license":15,"keywords":49,"description":7},[26,34,33,32,31,30,29,28,50,27],"chunking","mine","1e7390741dffd3d5041fca4ebc056071ee3ba137","archives\u002Fuser-installed-skills\u002Fbioc-fulltext-fetch\u002Fc1d806facb0db73a\u002Fbioc-fulltext-fetch",5,"https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub","https:\u002F\u002Fgithub.com\u002FEthanYoQ","www.orz.md","Part Marketer, Part AI Explorer.","2026-11-07 14:52:29",{"source":8,"skillName":5,"commands":61,"prompt":71,"zipAvailable":72},[62,65,68],{"tool":63,"command":64},"npx","npx skills add EthanYoQ\u002FSkill-hub --skill bioc-fulltext-fetch",{"tool":66,"command":67},"bunx","bunx skills add EthanYoQ\u002FSkill-hub --skill bioc-fulltext-fetch",{"tool":69,"command":70},"pnpm","pnpm dlx skills add EthanYoQ\u002FSkill-hub --skill bioc-fulltext-fetch","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Farchives\u002Fuser-installed-skills\u002Fbioc-fulltext-fetch\u002Fc1d806facb0db73a\u002Fbioc-fulltext-fetch\u002FSKILL.md",true,[],[75,103,133,157,175,190],{"skillNo":76,"name":77,"nameZh":6,"description":78,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":82,"publishedAt":44},"01M713CBDSYARG4PXFXK2R2NNN","xhs-pharma-social-listening","Run evidence-disciplined Xiaohongshu social listening for foreign pharma employee pain points. Use when the user asks to collect and analyze notes\u002Fcomments about pharma companies, aliases, roles, compliance, medical affairs, or AI topics, then produce a cited and scored sampled-insight report. Includes independent-evidence counting, MediaCrawler batch collection, and CAPTCHA\u002F461 fallback to Agent Reach\u002FOpenCLI.","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fxhs-pharma-social-listening\u002FSKILL.md",[81],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102],"social","xhs pharma social listening","social listening","xhs","medical","counting","when","collection","batch","analyze","notes","run","opencli","fallback","captcha","agent reach","evidence","for","collect","produce",{"skillNo":104,"name":105,"nameZh":6,"description":106,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":115,"publishedAt":44},"01M713C8Q76ZP0WXXBRAFKS6EE","healthcare-phi-compliance","Protected Health Information (PHI) and Personally Identifiable Information (PII) compliance patterns for healthcare applications. Covers data classification, access control, audit trails, encryption, and common leak vectors. Use when code touches PHI or PII in a healthcare system, or when auditing access control, audit trails, or leak vectors.","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fhealthcare-phi-compliance\u002FSKILL.md",[109,110],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":111,"categoryName":112,"taxonomyType":113,"taxonomyTypeDesc":114},"01JBSEEDCAT000000000000037","律师与法务","PROFESSION","适用职业",[116,117,118,119,120,121,122,39,89,123,124,125,126,127,128,129,100,130,131,132],"healthcare","phi","compliance","healthcare phi compliance","pii","compliance patterns","information","encryption","code","common","audit","data","patterns","Access Control","system","applications","Auditing Access Control",{"skillNo":134,"name":135,"nameZh":6,"description":136,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":137,"categories":138,"keywords":140,"publishedAt":44},"01M713C5R2GE43X45T2K75TB76","healthcare-eval-harness","Patient safety evaluation harness for healthcare application deployments. Automated test suites for CDSS accuracy, PHI exposure, clinical workflow integrity, and integration compliance. Blocks deployments on safety failures. Use when a healthcare deployment must be gated on patient-safety tests for CDSS accuracy, PHI exposure, and workflow integrity.","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fhealthcare-eval-harness\u002FSKILL.md",[139],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[116,141,142,143,144,145,146,89,147,148,149,150,117,100,151,152,153,154,155,156],"eval","harness","healthcare eval harness","eval harness","clinical","patient","workflow","Evaluation","Safety","integrity","deployment","accuracy","tests","deployments","use","super",{"skillNo":158,"name":159,"nameZh":6,"description":160,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":161,"categories":162,"keywords":164,"publishedAt":44},"01M713C2J17S91VYR7A8M696VR","healthcare-emr-patterns","EMR\u002FEHR development patterns for healthcare applications. Clinical safety, encounter workflows, prescription generation, clinical decision support integration, and accessibility-first UI for medical data entry. Use when building EMR or EHR features such as encounter workflows, prescription generation, or clinical data entry UI.","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fhealthcare-emr-patterns\u002FSKILL.md",[163],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[116,165,128,166,167,87,145,168,89,169,170,171,149,127,172,173,174,100,131,155],"EMR","healthcare emr patterns","decision","workflows","support","clinical decision support","features","ui","as","development",{"skillNo":176,"name":177,"nameZh":6,"description":178,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":179,"categories":180,"keywords":182,"publishedAt":44},"01M713BZERTXV0N8X6SW9DDVWZ","healthcare-cdss-patterns","Clinical Decision Support System (CDSS) development patterns. Drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), alert severity classification, and integration into EMR workflows. Use when building clinical decision support — drug interaction checks, dose validation, clinical scoring, or alert severity.","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fhealthcare-cdss-patterns\u002FSKILL.md",[181],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[116,128,183,167,145,165,168,89,184,185,169,170,186,174,130,187,155,188,156,189],"healthcare cdss patterns","scoring","interaction","severity","classification","alert","decision support",{"skillNo":191,"name":192,"nameZh":6,"description":193,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":194,"categories":195,"keywords":197,"publishedAt":44},"01M713BVK3PQR7V4ZKYGMWMXJD","dxy-task-clarify","用户的请求含糊、缺背景、缺资料说明、缺读者或用途时使用。先用少量带选项的追问把任务问清楚，写成任务卡，用户确认后再执行。尤其适用于医生、科研人员和药企各岗位的工作，例如「帮我分析一下这些数据」「帮我准备明天的拜访」「写个总结」「做个 PPT」「回一下这封邮件」「整理一下会议纪要」「整理一下这些文献」「帮我准备下周的讲课」「写个病例汇报」「帮我写个提示词」。用户说「先问我」「帮我理清需求」「我不知道怎么描述」「我不会问 AI」时也使用。用途、资料、读者都已说清的请求，纯翻译，以及写代码、改代码，不使用。","https:\u002F\u002Fgithub.com\u002FEthanYoQ\u002FSkill-hub\u002Fblob\u002Fmine\u002Fskills\u002F10-business-industry\u002Fdxy-task-clarify\u002FSKILL.md",[196],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[198,199,200,201,202,203,204,205],"clarify","dxy task clarify","task","pptx","整理","翻译","数据","总结"]