# lab-parser-normalize **Repository Path**: medical-light-years/lab-parser-normalize ## Basic Information - **Project Name**: lab-parser-normalize - **Description**: 面向医院设备科的医技仪器解析归一工具,上传厂家样例 PDF/Excel/CSV 后系统并行跑规则解析与视觉识别,按院内 LIS 主数据字典归一字段、换算单位、走\"草稿→待确认→已确认→已导出\"状态机并全程审计留痕,最终输出可直接灌进 HIS 的对接模板,把单台仪器接入 LIS 从 4–12 周压缩到半天。 - **Primary Language**: TypeScript - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-23 - **Last Updated**: 2026-08-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 医技检验仪器解析归一系统 > 设备科上传一份仪器样例(PDF / Excel / CSV),系统自动识别字段并归一到院内 LIS 主数据字典,工程师复核确认后导出对接 LIS —— 把单台仪器接入 LIS 从 4–12 周压缩到半天内。 面向中国医院设备科、信息科与检验科,把"各家仪器 PDF/Excel/CSV 各长各的样子"这件烦事,做成一条可复用、可审计、可批量跑的流水线。 ## 适用场景 / 目标岗位 | 岗位 | 什么时候用 | 得到什么 | |------|------------|----------| | 设备科工程师 | 新仪器到货,需要把厂家样例报告的字段映射到院内 LIS 主数据 | 一份可下载的 Excel/JSON 接入模板(按 LIS 工程师约定的列序),含字段、单位、换算因子、原始来源 | | 信息科 LIS 工程师 | 收到设备科提交的接入模板,需把字段批量灌进 HIS / LIS 字典 | 状态化的字段清单(草稿 / 待确认 / 已确认 / 已导出),每条都有 audit 留痕与人工确认人 | | 检验科 | 仪器升级或新批号,需要重新核对报告字段是否还匹配字典 | 三路交叉校验(OCR / 视觉 / 规则)后的字段表,冲突字段自动标 issue | | 设备科长 / 信息科长 | 月度盘点:还有哪些仪器没接入、哪些字段待确认 | 按仪器状态分组的汇总 + 待处理 issues 列表 | > **表格纪律**:本表只写岗位口语 + 业务时刻 + 可核对产出;HL7 / ASTM / REST 路径、状态枚举、webhook 参数等一律放到「命令 / API / 配置说明」。 ## 能力要点 - **多源并行解析**:同一份仪器样例同时走 PDF 规则解析 + LLM 视觉识别,按字段名/单位/参考范围做三路交叉校验 - **强类型字典归一**:院内 LIS 主数据字典用 Zod 强约束,canonical_code + 单位 + 参考范围一进字典就校验,字段映射走"字典精确 → alias → LLM 兜底"三层 fallback - **单位换算**:内置常见检验单位换算表(mg/dL ↔ mmol/L 葡萄糖、mg/dL ↔ μmol/L 肌酐、g/dL ↔ g/L 血红蛋白等),不支持时显式标 issue 并保留原值 - **状态化推进**:归一记录走 `草稿 → 待确认 → 已确认 → 已导出` 状态机,非法迁移抛错并写 audit - **正式对接层**:导出按信息科约定列序的 Excel / JSON 模板,列名冻结 / 自动筛选,可直接灌进 HIS - **本地优先 + 模型可选**:默认规则解析器 + LLM 视觉识别作为增强层;模型不可达时降级到规则结果,不阻断业务 ## 快速开始 ```bash # 1. 安装依赖 npm install # 2. 复制配置(首次启动) cp .env.example .env # 生成 32 字节加密密钥并写入 .env ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64) sed -i.bak "s|^ENCRYPTION_KEY=.*|ENCRYPTION_KEY=${ENCRYPTION_KEY}|" .env && rm .env.bak # 3. 注册一台仪器 npx tsx src/cli.ts instrument add --model "Roche cobas c503" --vendor "Roche" --serial "RC503-2026-001" # 4. 上传一份脱敏样例(test/golden/ 下自带 3 份厂家样例) npx tsx src/cli.ts parse test/golden/roche-cobas-sample.pdf # 5. 工程师复核确认 npx tsx src/cli.ts confirm --batch --user dev01 # 6. 导出对接模板 npx tsx src/cli.ts export --format xlsx # 7. 看仪器当前状态 npx tsx src/cli.ts status # 8. 一键体验全链路(无需真实模型,LLM 用 mock 替身) bash scripts/demo.sh ``` ### 部署方式(两选一) **路径 A — Node 直跑(推荐用于信息科本地验证)**: ```bash npm install npm run build # tsc → dist/ npm start # node dist/cli.js npm run server # 启动 HTTP API(端口 7180) ``` **路径 B — Docker compose(推荐用于内网部署)**: ```bash docker build -t lab-parser-normalize:latest . docker run -d --name lab-norm \ -p 7180:7180 \ -v $(pwd)/.env:/app/.env:ro \ -v $(pwd)/data:/app/data \ -v $(pwd)/logs:/app/logs \ lab-parser-normalize:latest ``` `Dockerfile` 基于 `node:20-alpine`;`CMD` 默认启动 HTTP API 服务;如需 CLI 模式可 `docker run --rm lab-parser-normalize:latest node dist/cli.js --help`。完整构建排除见 `.dockerignore`(不打包 `node_modules / dist / logs / data / test / coverage / .env`)。 ## 命令 / API / 配置说明 ### CLI 子命令 | 子命令 | 作用 | 关键参数 | |--------|------|----------| | `lab-norm parse ` | 上传样例 → 解析 → 落 draft → 写 issues.json | `` 仪器元数据,`` 仪器报告(pdf/xlsx/csv/md) | | `lab-norm confirm ` | 人工确认推进到 confirmed | `--user` 操作人;`--batch` 确认全部待办 | | `lab-norm export ` | 仅导出 confirmed → Excel/JSON 模板 | `--format xlsx\|json` | | `lab-norm status ` | 打印仪器状态机各状态计数 + 待处理 issues | 输出 JSON 便于管道 | | `lab-norm instrument add/list/get/update` | 仪器档案 CRUD | `--model` 型号;`--vendor` 厂家;`--serial` 序列号 | | `lab-norm issues ` | 按 severity / kind 过滤 issues | `--severity error\|warning\|info`;`--kind three_way_conflict` 等 | ### HTTP API(默认端口 7180,鉴权头 `X-Lab-Key`) | 方法 | 路径 | 作用 | |------|------|------| | POST | `/v1/parse` | multipart 上传样例 + 仪器元数据;返回 `{ instr_id, items, issues }` | | POST | `/v1/items/:id/confirm` | 人工确认;body `{ user }` | | POST | `/v1/export` | body `{ instr_id, format }`;返回文件流 | | GET | `/v1/instruments/:id/status` | 按状态分组 + issues 计数 | | GET | `/v1/instruments` | 仪器档案列表 | | POST | `/v1/instruments` | 新增仪器档案 | | PATCH | `/v1/instruments/:id/status` | 改仪器档案状态 | | GET | `/healthz` | 健康检查(无需鉴权) | ### 配置(`.env`) | 变量 | 必填 | 说明 | |------|------|------| | `ENCRYPTION_KEY` | 是 | 32 字节 base64;生成命令见「快速开始」 | | `MODEL_BASE_URL` | 否 | OpenAI-compatible 端点;未配置时降级到规则解析 | | `MODEL_API_KEY` | 否 | 模型 API key;本项目仅协议实现 | | `MODEL_NAME` | 否 | 模型名(如 qwen2-vl / gpt-4o) | | `MODEL_TIMEOUT_MS` | 否 | 模型超时,默认 30000 | | `DICT_PATH` | 否 | 院内 LIS 主数据字典 JSON 路径 | | `DB_PATH` | 否 | SQLite 状态库路径 | | `AUDIT_PATH` | 否 | audit.jsonl 路径 | | `API_KEY` | 否 | HTTP API 鉴权 key | | `API_PORT` | 否 | HTTP API 端口,默认 7180 | | `LAB_PARSER_DISABLE` | 否 | 逗号分隔禁用的解析器名(如 `llm-vision`) | ## 典型场景 ### 场景一:新仪器接入(设备科 → 信息科) 1. 设备科拿到厂家发来的「Roche cobas c503 报告样例 PDF」(通常 5–20 页,含厂家奇怪的字段名) 2. 设备科用 `lab-norm parse ` 上传样例 3. 系统自动识别字段、按院内 LIS 主数据字典归一、把命中的字段写 `草稿` 状态、命不中的写 `issues.json`(含冲突与建议) 4. 设备科在仪器档案页核对命中字段 → 用 `lab-norm confirm --batch --user dev01` 一键确认 5. 设备科用 `lab-norm export --format xlsx` 导出 Excel 模板(按信息科约定的列序)→ 微信发给信息科 6. 信息科 LIS 工程师把 Excel 灌进 HIS 字典 → 完成接入 ### 场景二:仪器升级后的字段核对(检验科) 1. 检验科发现仪器升级后报告字段名变了(如新增 `GLU-PLUS`) 2. 设备科重新上传一份新报告样例(`lab-norm parse`) 3. 系统走三路交叉校验,把"OCR 有 / 视觉有 / 规则解析没有"的字段自动标 `ocr_missing` issue 4. 检验科与设备科一起看 issues 列表,判断新字段是否需要加进院内 LIS 主数据字典 5. 字典更新后再次跑 `parse`,新字段命中且 confidence=medium → 走 `confirm` 流程 ### 场景三:批量接入多台同型号仪器(信息科) 1. 信息科有 10 台同型号仪器需要接入 2. 设备科上传 1 份样例 → 归一 → 工程师确认 1 次 → 导出 Excel 模板 3. 把模板复制 10 份,分别修改 `serial` 字段后批量灌进 HIS 4. 全程 audit.jsonl 留痕,事后可追溯每台仪器的接入人 / 接入时间 / 字段来源 ## 输出样例 ### Excel 导出模板(脱敏片段) | canonical_code | instr_field | unit | factor | source_ref | confirmed_by | confirmed_at | 备注 | |----------------|-------------|------|--------|------------|--------------|--------------|------| | GLU | Glucose | mmol/L | 1.0 | cobas-c503.pdf#p3#tbl1 | dev01 | 2026-08-15T10:30:00+08:00 | 厂家字段名 `Glucose` 经 alias 匹配 | | CHOL | Total Cholesterol | mmol/L | 1.0 | cobas-c503.pdf#p3#tbl1 | dev01 | 2026-08-15T10:30:01+08:00 | | | Cr | Creatinine | μmol/L | 1.0 | cobas-c503.pdf#p3#tbl1 | dev01 | 2026-08-15T10:30:02+08:00 | | | BUN | Urea Nitrogen | mmol/L | 2.8 | cobas-c503.pdf#p3#tbl1 | dev01 | 2026-08-15T10:30:03+08:00 | 单位 mg/dL → mmol/L,factor=2.8 | ### Issues 汇总(脱敏片段) ```json { "instr_id": "RC503-2026-001", "issues": [ { "kind": "three_way_conflict", "severity": "error", "field_raw": "GLU-PLUS", "suggestion": "OCR 识别为 'GLU-PLUS'、视觉识别为 'Glucose Plus'、规则解析为空;建议人工确认是否为新字段", "source_chain": ["ocr", "vision", "rule"] }, { "kind": "unit_unknown", "severity": "warning", "field_raw": "TSH", "unit_raw": "mIU/mL", "suggestion": "TSH 在字典默认单位 mIU/L,请确认换算关系或更新字典" } ] } ``` ### CLI status 输出(脱敏片段) ```json { "instr_id": "RC503-2026-001", "status_counts": { "draft": 0, "pending": 2, "confirmed": 18, "exported": 0 }, "issues_pending": 2 } ``` 完整样例文件位于 `docs/examples/outputs/`。 ## 架构与数据流 ``` ┌──────────────┐ 上传 ┌─────────────────┐ │ 仪器样例文件 │────────▶│ 文件类型分发器 │ │ PDF/Excel/CSV│ │ (扩展名+magic) │ └──────────────┘ └────────┬────────┘ │ ▼ ┌────────────────────────────────┐ │ 解析器注册表(可插拔) │ │ ┌─────────────┐ ┌──────────┐ │ │ │ MarkdownTable │ │ LLMVision│ │ │ │ (规则兜底) │ │ (可选) │ │ │ └─────────────┘ └──────────┘ │ └─────────────┬──────────────────┘ │ 三路结果 ▼ ┌────────────────────────────────┐ │ 三路合并 + 冲突场景 + 置信度 │ │ (high/medium/low/unknown) │ └─────────────┬──────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 字段映射引擎(字典+alias+LLM) │ │ + 单位换算(含回退与推断) │ └─────────────┬──────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 状态机 (草稿→待确认→已确认→导出) │ │ + SQLite 存储(事务+版本锁) │ └─────────────┬──────────────────┘ │ ▼ ┌────────────────────────────────┐ │ 导出 Excel/JSON 模板 │ │ + audit.jsonl 留痕 │ └────────────────────────────────┘ ``` 详细架构与数据流描述见 `docs/architecture.md`(Phase 4 后续 task 补齐)。 ## 安全与合规边界 - **不输出诊疗建议**:本项目只做仪器报告字段的解析、归一与导出,不读取患者信息,不参与临床诊断与治疗决策 - **不自动写入 EMR / HIS**:所有归一记录均需人工显式 `confirm`,状态推进由操作人触发并写入 audit - **不绕过人工复核**:LLM 视觉识别仅作为字段名归一的"增强层",其输出始终落到 `draft` / `pending` 状态,必须由工程师人工确认后才会被导出 - **敏感字段加密**:仪器档案、字典、审计敏感字段用 AES-256-CBC 加密存储,启动校验 32 字节密钥 - **本地优先**:模型可选,未配置时系统走纯规则路径(多源并行解析 + 三路合并 + 字典匹配),不依赖外部服务即可完成核心接入流程 ## 项目结构 ``` lab-parser-normalize/ ├── README.md # 中文产品说明(本文件) ├── README_EN.md # 英文版产品说明(Phase 5 补齐) ├── package.json # 依赖清单(fastify + zod + better-sqlite3 + exceljs + openai 协议 SDK) ├── tsconfig.json # TypeScript 严格模式 + ES2022 ├── vitest.config.ts # Vitest 测试配置 ├── .env.example # 配置模板(含密钥生成说明) ├── .gitignore # 排除运行时数据与构建产物 ├── .dockerignore # Docker 构建排除 ├── Dockerfile # (可选)node:20-alpine 镜像 ├── docker-compose.yaml # (可选)单服务编排 ├── qrcode.jpg # 公众号二维码 ├── LICENSE # MIT ├── src/ │ ├── cli.ts # CLI 入口(parse/confirm/export/status/instrument/issues) │ ├── server.ts # HTTP API 启动 │ ├── api.ts # Fastify 路由(POST /v1/parse、confirm、export 等) │ ├── pipeline/ # 解析管线集成层 │ ├── parser/ # 解析器抽象 + 注册表 + 分发器 + 实现(markdown-table/llm-vision) │ ├── mapping/ # 三路合并 + 字段映射引擎 + 单位换算 │ ├── dictionary/ # 院内 LIS 主数据字典(Zod schema + loader + alias 索引 + seed) │ ├── state/ # 状态机 + SQLite 存储 │ ├── export/ # Excel/JSON 导出模板 │ ├── security/ # AES-256-CBC 字段加密 │ ├── audit/ # audit.jsonl 审计日志(行追加 + 互斥锁) │ ├── gateway/ # OpenAI-compatible ModelGateway(统一协议 + 降级 + 审计元数据) │ └── issues/ # Issues 暴露(汇总 + severity + CLI 整合) ├── test/ # Vitest 单测 + golden 样例 + e2e 集成 │ └── golden/ # 厂家脱敏样例(Roche/Beckman/Sysmex/Mindray) ├── docs/ │ └── examples/outputs/ # 脱敏输出样例 ├── scripts/ │ └── demo.sh # 一键体验脚本(cp .env.example → 生成密钥 → parse → confirm → export) └── logs/ # 运行时 audit.jsonl(git 已忽略) ``` ## License MIT --- ## 关注我们 欢迎扫码关注公众号,获取项目更新与交流加群: ![关注我们](qrcode.jpg)