ehr-data-parsing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEHR Data Parsing
EHR 数据解析
Overview
概述
This skill encodes Python pipelines for parsing clinical data from two
dominant formats: HL7v2 (pipe-delimited messages) and FHIR R4 (JSON
resources). It covers extraction of patient demographics, lab results,
diagnoses, and medications, plus format conversion and data quality checks.
Scope:
- HL7v2 message parsing (MSH, PID, PV1, OBR, OBX segments)
- FHIR R4 resource extraction (Patient, Observation, Condition, MedicationRequest)
- HL7v2 → tabular (CSV/DataFrame) conversion
- FHIR Bundle iteration and flattening
- Data quality checks (completeness, date validation, code system verification)
Out of scope: HL7v3/CDA parsing, FHIR server deployment, SMART on FHIR
authentication, real-time message routing.
本 skill 提供了 Python 流水线,用于解析两种主流格式中的临床数据:HL7v2(管道符分隔的消息)和 FHIR R4(JSON 资源)。它涵盖患者基本信息、实验室结果、诊断和用药信息的提取,以及格式转换和数据质量检查。
范围:
- HL7v2 消息解析(MSH、PID、PV1、OBR、OBX segment)
- FHIR R4 资源提取(Patient、Observation、Condition、MedicationRequest)
- HL7v2 → 表格数据(CSV/DataFrame)转换
- FHIR Bundle 迭代与扁平化
- 数据质量检查(完整性、日期验证、代码系统验证)
不在范围:HL7v3/CDA 解析、FHIR server 部署、SMART on FHIR 认证、实时消息路由。
Usage
用法
Invoke when the user asks to:
- Parse an HL7v2 message or batch file
- Extract patient demographics, labs, diagnoses, or medications from FHIR
- Convert HL7v2 messages to CSV or pandas DataFrames
- Flatten FHIR Bundles into tabular format
- Run quality checks on clinical data extracts
The skill emits Python code using for HL7v2 and
for FHIR R4. Install dependencies:
python-hl7fhir.resourcesbash
pip install python-hl7 fhir.resources pandas在用户要求执行以下操作时调用:
- 解析 HL7v2 消息或批处理文件
- 从 FHIR 中提取患者基本信息、实验室结果、诊断或用药信息
- 将 HL7v2 消息转换为 CSV 或 pandas DataFrame
- 将 FHIR Bundle 扁平化为表格格式
- 对临床数据提取结果运行质量检查
该 skill 输出的 Python 代码使用 解析 HL7v2,并使用 解析 FHIR R4。安装依赖:
python-hl7fhir.resourcesbash
pip install python-hl7 fhir.resources pandasResponse Format
响应格式
- Lead with the command or code the user needs — explain after
- Structure as: confirm inputs → working code → key parameters explained → gotchas
- One complete working example per task; do not show every alternative
- Keep code comments minimal and functional (what, not why-it-exists)
- Target: 50-100 lines of code with brief surrounding explanation
- 先提供用户所需的命令或代码——随后再进行说明
- 结构:确认输入 → 可运行的代码 → 关键参数说明 → 注意事项
- 每项任务提供一个完整可运行示例;不要展示所有备选方案
- 代码注释应精简且实用(说明做什么,而非说明为何存在)
- 目标:代码 50–100 行,并附简短说明
Core Concepts
核心概念
Parser Selection Decision Tree
解析器选择决策树
What is the input format?
├─ HL7v2 (pipe-delimited, segments start with MSH|PID|OBX...)
│ ├─ Message type?
│ │ ├─ ORU^R01 (lab results) → extract OBR + OBX segments
│ │ ├─ ADT^A01/A08 (admit/update) → extract PID + PV1 segments
│ │ └─ ORM^O01 (orders) → extract ORC + OBR segments
│ └─ Library: python-hl7 (parse → segment → field access)
└─ FHIR R4 (JSON resources)
├─ Single resource → fhir.resources model_validate(json)
└─ Bundle → iterate entry[], check resource_type, follow pagination (link.next)
├─ Extract by system URI, not array position
└─ Check choice types (valueQuantity vs valueCodeableConcept)What is the input format?
├─ HL7v2 (pipe-delimited, segments start with MSH|PID|OBX...)
│ ├─ Message type?
│ │ ├─ ORU^R01 (lab results) → extract OBR + OBX segments
│ │ ├─ ADT^A01/A08 (admit/update) → extract PID + PV1 segments
│ │ └─ ORM^O01 (orders) → extract ORC + OBR segments
│ └─ Library: python-hl7 (parse → segment → field access)
└─ FHIR R4 (JSON resources)
├─ Single resource → fhir.resources model_validate(json)
└─ Bundle → iterate entry[], check resource_type, follow pagination (link.next)
├─ Extract by system URI, not array position
└─ Check choice types (valueQuantity vs valueCodeableConcept)HL7v2 Message Structure
HL7v2 消息结构
HL7v2 messages are pipe-delimited, segment-based. Each segment starts with
a 3-character identifier followed by fields separated by .
|HL7v2 消息以 segment 为单位并使用管道符分隔。每个 segment 以一个 3 字符标识符开头,字段之间使用 分隔。
|Key segments
关键 segment
| Segment | Name | Contains |
|---|---|---|
| MSH | Message Header | Sending/receiving app, message type, timestamp, version |
| PID | Patient Identification | MRN, name, DOB, sex, address, phone |
| PV1 | Patient Visit | Visit number, patient class, attending physician |
| OBR | Observation Request | Order number, test ordered, ordering provider |
| OBX | Observation Result | Result value, units, reference range, abnormal flag |
| DG1 | Diagnosis | Diagnosis code, type, description |
| AL1 | Allergy | Allergen, reaction, severity |
| Segment | 名称 | 包含内容 |
|---|---|---|
| MSH | 消息头 | 发送/接收应用、消息类型、时间戳、版本 |
| PID | 患者标识 | MRN、姓名、出生日期、性别、地址、电话 |
| PV1 | 患者就诊 | 就诊编号、患者类别、主治医生 |
| OBR | 观察申请 | 申请编号、申请检查、申请医生 |
| OBX | 观察结果 | 结果值、单位、参考范围、异常标记 |
| DG1 | 诊断 | 诊断代码、类型、描述 |
| AL1 | 过敏 | 过敏原、反应、严重程度 |
Common message types
常见消息类型
| Type | Trigger | Use |
|---|---|---|
| ADT^A01 | Admit | Patient admission |
| ADT^A08 | Update | Patient information update |
| ORU^R01 | Observation | Lab/radiology results |
| ORM^O01 | Order | New order placed |
| MDM^T02 | Document | Document notification |
| 类型 | 触发操作 | 用途 |
|---|---|---|
| ADT^A01 | 入院 | 患者入院 |
| ADT^A08 | 更新 | 患者信息更新 |
| ORU^R01 | 观察 | 实验室/放射学结果 |
| ORM^O01 | 医嘱 | 创建新医嘱 |
| MDM^T02 | 文档 | 文档通知 |
1. Parse HL7v2 Messages
1. 解析 HL7v2 消息
python
import hl7
raw = (
"MSH|^~\\&|LAB|HOSP|EHR|HOSP|20240115120000||ORU^R01|MSG001|P|2.5\r"
"PID|1||MRN123^^^HOSP^MR||DOE^JOHN^A||19800315|M|||"
"123 MAIN ST^^ANYTOWN^CA^90210\r"
"OBR|1|ORD001||CBC^Complete Blood Count^L|||20240115080000\r"
"OBX|1|NM|WBC^White Blood Cell Count^L||7.5|10*3/uL|4.5-11.0|N|||F\r"
"OBX|2|NM|HGB^Hemoglobin^L||14.2|g/dL|12.0-17.5|N|||F\r"
"OBX|3|NM|PLT^Platelet Count^L||250|10*3/uL|150-400|N|||F\r"
)
msg = hl7.parse(raw)python
import hl7
raw = (
"MSH|^~\\&|LAB|HOSP|EHR|HOSP|20240115120000||ORU^R01|MSG001|P|2.5\r"
"PID|1||MRN123^^^HOSP^MR||DOE^JOHN^A||19800315|M|||"
"123 MAIN ST^^ANYTOWN^CA^90210\r"
"OBR|1|ORD001||CBC^Complete Blood Count^L|||20240115080000\r"
"OBX|1|NM|WBC^White Blood Cell Count^L||7.5|10*3/uL|4.5-11.0|N|||F\r"
"OBX|2|NM|HGB^Hemoglobin^L||14.2|g/dL|12.0-17.5|N|||F\r"
"OBX|3|NM|PLT^Platelet Count^L||250|10*3/uL|150-400|N|||F\r"
)
msg = hl7.parse(raw)Extract patient demographics from PID
从 PID 提取患者基本信息
python
def extract_pid(msg):
pid = msg.segment("PID")
return {
"mrn": str(pid(3)(0)(0)), # PID-3.1: patient ID
"last_name": str(pid(5)(0)(0)), # PID-5.1: family name
"first_name": str(pid(5)(0)(1)), # PID-5.2: given name
"dob": str(pid(7)), # PID-7: date of birth
"sex": str(pid(8)), # PID-8: administrative sex
}
demographics = extract_pid(msg)python
def extract_pid(msg):
pid = msg.segment("PID")
return {
"mrn": str(pid(3)(0)(0)), # PID-3.1: patient ID
"last_name": str(pid(5)(0)(0)), # PID-5.1: family name
"first_name": str(pid(5)(0)(1)), # PID-5.2: given name
"dob": str(pid(7)), # PID-7: date of birth
"sex": str(pid(8)), # PID-8: administrative sex
}
demographics = extract_pid(msg){'mrn': 'MRN123', 'last_name': 'DOE', 'first_name': 'JOHN',
{'mrn': 'MRN123', 'last_name': 'DOE', 'first_name': 'JOHN',
'dob': '19800315', 'sex': 'M'}
'dob': '19800315', 'sex': 'M'}
undefinedundefinedPID field reference
PID 字段参考
| Field | Position | Format |
|---|---|---|
| Patient ID | PID-3 | CX datatype (ID^^^authority^type) |
| Patient Name | PID-5 | XPN: Family^Given^Middle |
| Date of Birth | PID-7 | YYYYMMDD |
| Sex | PID-8 | M, F, O, U |
| Address | PID-11 | XAD: Street^City^State^Zip |
| Phone | PID-13 | XTN datatype |
| 字段 | 位置 | 格式 |
|---|---|---|
| 患者 ID | PID-3 | CX 数据类型(ID^^^authority^type) |
| 患者姓名 | PID-5 | XPN:Family^Given^Middle |
| 出生日期 | PID-7 | YYYYMMDD |
| 性别 | PID-8 | M、F、O、U |
| 地址 | PID-11 | XAD:Street^City^State^Zip |
| 电话 | PID-13 | XTN 数据类型 |
Extract lab results from OBX segments
从 OBX segment 提取实验室结果
python
def extract_obx_results(msg):
results = []
for segment in msg:
if str(segment(0)) == "OBX":
results.append({
"set_id": str(segment(1)),
"value_type": str(segment(2)), # NM=numeric, ST=string, CE=coded
"test_code": str(segment(3)(0)(0)), # OBX-3.1: identifier
"test_name": str(segment(3)(0)(1)), # OBX-3.2: text
"value": str(segment(5)), # OBX-5: observation value
"units": str(segment(6)), # OBX-6: units
"ref_range": str(segment(7)), # OBX-7: reference range
"abnormal_flag": str(segment(8)), # OBX-8: N/H/L/A
"status": str(segment(11)), # OBX-11: F=final, P=preliminary
})
return results
labs = extract_obx_results(msg)python
def extract_obx_results(msg):
results = []
for segment in msg:
if str(segment(0)) == "OBX":
results.append({
"set_id": str(segment(1)),
"value_type": str(segment(2)), # NM=numeric, ST=string, CE=coded
"test_code": str(segment(3)(0)(0)), # OBX-3.1: identifier
"test_name": str(segment(3)(0)(1)), # OBX-3.2: text
"value": str(segment(5)), # OBX-5: observation value
"units": str(segment(6)), # OBX-6: units
"ref_range": str(segment(7)), # OBX-7: reference range
"abnormal_flag": str(segment(8)), # OBX-8: N/H/L/A
"status": str(segment(11)), # OBX-11: F=final, P=preliminary
})
return results
labs = extract_obx_results(msg)OBX value types and status codes
OBX 值类型和状态代码
| OBX-2 | Meaning | OBX-8 | Meaning | OBX-11 | Meaning |
|---|---|---|---|---|---|
| NM | Numeric | N | Normal | F | Final |
| ST | String | H | High | P | Preliminary |
| CE | Coded entry | L | Low | C | Corrected |
| OBX-2 | 含义 | OBX-8 | 含义 | OBX-11 | 含义 |
|---|---|---|---|---|---|
| NM | 数值 | N | 正常 | F | 最终 |
| ST | 字符串 | H | 高 | P | 初步 |
| CE | 编码条目 | L | 低 | C | 已更正 |
2. Batch HL7v2 Processing
2. 批量处理 HL7v2
python
import hl7
import pandas as pd
def parse_hl7_file(filepath):
"""Parse a file containing multiple HL7v2 messages separated by blank lines."""
with open(filepath, "r") as f:
content = f.read()
raw_messages = content.strip().split("\nMSH|")
raw_messages = [raw_messages[0]] + ["MSH|" + m for m in raw_messages[1:]]
all_results = []
for raw in raw_messages:
raw = raw.strip().replace("\n", "\r")
if not raw:
continue
msg = hl7.parse(raw)
demo = extract_pid(msg)
for lab in extract_obx_results(msg):
all_results.append({**demo, **lab})
return pd.DataFrame(all_results)
df = parse_hl7_file("lab_results.hl7")python
import hl7
import pandas as pd
def parse_hl7_file(filepath):
"""Parse a file containing multiple HL7v2 messages separated by blank lines."""
with open(filepath, "r") as f:
content = f.read()
raw_messages = content.strip().split("\nMSH|")
raw_messages = [raw_messages[0]] + ["MSH|" + m for m in raw_messages[1:]]
all_results = []
for raw in raw_messages:
raw = raw.strip().replace("\n", "\r")
if not raw:
continue
msg = hl7.parse(raw)
demo = extract_pid(msg)
for lab in extract_obx_results(msg):
all_results.append({**demo, **lab})
return pd.DataFrame(all_results)
df = parse_hl7_file("lab_results.hl7")3. FHIR R4 Resource Extraction
3. FHIR R4 资源提取
Parse a Patient resource
解析 Patient resource
python
from fhir.resources.patient import Patient
patient_json = {
"resourceType": "Patient",
"id": "example-001",
"identifier": [
{"system": "http://hospital.example.org/mrn", "value": "MRN123"}
],
"name": [{"family": "Doe", "given": ["John", "A"]}],
"gender": "male",
"birthDate": "1980-03-15",
"address": [
{"line": ["123 Main St"], "city": "Anytown", "state": "CA", "postalCode": "90210"}
],
}
patient = Patient.model_validate(patient_json)
demographics = {
"id": patient.id,
"mrn": patient.identifier[0].value if patient.identifier else None,
"last_name": patient.name[0].family if patient.name else None,
"first_name": patient.name[0].given[0] if patient.name and patient.name[0].given else None,
"gender": patient.gender,
"birth_date": str(patient.birthDate) if patient.birthDate else None,
}python
from fhir.resources.patient import Patient
patient_json = {
"resourceType": "Patient",
"id": "example-001",
"identifier": [
{"system": "http://hospital.example.org/mrn", "value": "MRN123"}
],
"name": [{"family": "Doe", "given": ["John", "A"]}],
"gender": "male",
"birthDate": "1980-03-15",
"address": [
{"line": ["123 Main St"], "city": "Anytown", "state": "CA", "postalCode": "90210"}
],
}
patient = Patient.model_validate(patient_json)
demographics = {
"id": patient.id,
"mrn": patient.identifier[0].value if patient.identifier else None,
"last_name": patient.name[0].family if patient.name else None,
"first_name": patient.name[0].given[0] if patient.name and patient.name[0].given else None,
"gender": patient.gender,
"birth_date": str(patient.birthDate) if patient.birthDate else None,
}Extract Observation, Condition, MedicationRequest
提取 Observation、Condition 和 MedicationRequest
Pattern: → extract by system URI, not array position.
model_validate(json)python
from fhir.resources.observation import Observation
from fhir.resources.condition import Condition
from fhir.resources.medicationrequest import MedicationRequest
def extract_observation(obs_json):
obs = Observation.model_validate(obs_json)
return {
"id": obs.id, "status": obs.status,
"loinc_code": next((c.code for c in (obs.code.coding or []) if c.system == "http://loinc.org"), None),
"display": obs.code.coding[0].display if obs.code.coding else None,
"value": obs.valueQuantity.value if obs.valueQuantity else None,
"unit": obs.valueQuantity.unit if obs.valueQuantity else None,
"effective_date": str(obs.effectiveDateTime) if obs.effectiveDateTime else None,
"patient_ref": obs.subject.reference if obs.subject else None,
}
def extract_condition(cond_json):
cond = Condition.model_validate(cond_json)
return {
"id": cond.id,
"status": cond.clinicalStatus.coding[0].code if cond.clinicalStatus and cond.clinicalStatus.coding else None,
"snomed_code": next((c.code for c in (cond.code.coding or []) if c.system and "snomed" in c.system), None),
"icd10_code": next((c.code for c in (cond.code.coding or []) if c.system and "icd-10" in c.system), None),
"display": cond.code.coding[0].display if cond.code.coding else None,
"onset": str(cond.onsetDateTime) if cond.onsetDateTime else None,
"patient_ref": cond.subject.reference if cond.subject else None,
}
def extract_medication_request(med_json):
med = MedicationRequest.model_validate(med_json)
cc = med.medicationCodeableConcept
return {
"id": med.id, "status": med.status,
"rxnorm_code": cc.coding[0].code if cc and cc.coding else None,
"display": cc.coding[0].display if cc and cc.coding else None,
"dosage_text": med.dosageInstruction[0].text if med.dosageInstruction else None,
"authored_on": str(med.authoredOn) if med.authoredOn else None,
"patient_ref": med.subject.reference if med.subject else None,
}模式: → 按 system URI 提取,而不是依赖数组位置。
model_validate(json)python
from fhir.resources.observation import Observation
from fhir.resources.condition import Condition
from fhir.resources.medicationrequest import MedicationRequest
def extract_observation(obs_json):
obs = Observation.model_validate(obs_json)
return {
"id": obs.id, "status": obs.status,
"loinc_code": next((c.code for c in (obs.code.coding or []) if c.system == "http://loinc.org"), None),
"display": obs.code.coding[0].display if obs.code.coding else None,
"value": obs.valueQuantity.value if obs.valueQuantity else None,
"unit": obs.valueQuantity.unit if obs.valueQuantity else None,
"effective_date": str(obs.effectiveDateTime) if obs.effectiveDateTime else None,
"patient_ref": obs.subject.reference if obs.subject else None,
}
def extract_condition(cond_json):
cond = Condition.model_validate(cond_json)
return {
"id": cond.id,
"status": cond.clinicalStatus.coding[0].code if cond.clinicalStatus and cond.clinicalStatus.coding else None,
"snomed_code": next((c.code for c in (cond.code.coding or []) if c.system and "snomed" in c.system), None),
"icd10_code": next((c.code for c in (cond.code.coding or []) if c.system and "icd-10" in c.system), None),
"display": cond.code.coding[0].display if cond.code.coding else None,
"onset": str(cond.onsetDateTime) if cond.onsetDateTime else None,
"patient_ref": cond.subject.reference if cond.subject else None,
}
def extract_medication_request(med_json):
med = MedicationRequest.model_validate(med_json)
cc = med.medicationCodeableConcept
return {
"id": med.id, "status": med.status,
"rxnorm_code": cc.coding[0].code if cc and cc.coding else None,
"display": cc.coding[0].display if cc and cc.coding else None,
"dosage_text": med.dosageInstruction[0].text if med.dosageInstruction else None,
"authored_on": str(med.authoredOn) if med.authoredOn else None,
"patient_ref": med.subject.reference if med.subject else None,
}4. FHIR Bundle Flattening
4. FHIR Bundle 扁平化
python
import json
import pandas as pd
from fhir.resources.bundle import Bundle
def flatten_observation_bundle(bundle_json):
"""Extract all Observation resources from a FHIR Bundle into a DataFrame."""
bundle = Bundle.model_validate(bundle_json)
rows = [extract_observation(entry.resource.model_dump())
for entry in (bundle.entry or [])
if entry.resource.resource_type == "Observation"]
return pd.DataFrame(rows)
with open("observations_bundle.json") as f:
df = flatten_observation_bundle(json.load(f))python
import json
import pandas as pd
from fhir.resources.bundle import Bundle
def flatten_observation_bundle(bundle_json):
"""Extract all Observation resources from a FHIR Bundle into a DataFrame."""
bundle = Bundle.model_validate(bundle_json)
rows = [extract_observation(entry.resource.model_dump())
for entry in (bundle.entry or [])
if entry.resource.resource_type == "Observation"]
return pd.DataFrame(rows)
with open("observations_bundle.json") as f:
df = flatten_observation_bundle(json.load(f))5. Data Quality Checks
5. 数据质量检查
python
import pandas as pd
from datetime import datetime
def check_completeness(df, required_columns):
"""Report missing value rates for required columns."""
report = {}
for col in required_columns:
if col not in df.columns:
report[col] = {"status": "MISSING_COLUMN", "pct_complete": 0.0}
else:
pct = (df[col].notna().sum() / len(df)) * 100
report[col] = {
"status": "OK" if pct >= 95 else "WARNING" if pct >= 80 else "FAIL",
"pct_complete": round(pct, 1),
}
return report
def validate_dates(series, fmt="%Y-%m-%d", min_date="1900-01-01", max_date=None):
"""Validate date strings. Returns indices of invalid entries."""
max_dt = datetime.strptime(max_date, fmt) if max_date else datetime.now()
min_dt = datetime.strptime(min_date, fmt)
invalid = []
for idx, val in series.items():
if pd.isna(val):
continue
try:
dt = datetime.strptime(str(val)[:10], fmt)
if dt < min_dt or dt > max_dt:
invalid.append({"index": idx, "value": val, "reason": "out_of_range"})
except ValueError:
invalid.append({"index": idx, "value": val, "reason": "parse_error"})
return invalid
def verify_code_system(df, code_col, valid_codes, system_name="unknown"):
"""Check that coded values belong to an expected set."""
if code_col not in df.columns:
return {"status": "MISSING_COLUMN", "invalid_count": 0}
codes = df[code_col].dropna().unique()
invalid = [c for c in codes if c not in valid_codes]
return {
"system": system_name, "total_unique": len(codes),
"invalid_codes": invalid, "invalid_count": len(invalid),
"status": "OK" if len(invalid) == 0 else "FAIL",
}python
import pandas as pd
from datetime import datetime
def check_completeness(df, required_columns):
"""Report missing value rates for required columns."""
report = {}
for col in required_columns:
if col not in df.columns:
report[col] = {"status": "MISSING_COLUMN", "pct_complete": 0.0}
else:
pct = (df[col].notna().sum() / len(df)) * 100
report[col] = {
"status": "OK" if pct >= 95 else "WARNING" if pct >= 80 else "FAIL",
"pct_complete": round(pct, 1),
}
return report
def validate_dates(series, fmt="%Y-%m-%d", min_date="1900-01-01", max_date=None):
"""Validate date strings. Returns indices of invalid entries."""
max_dt = datetime.strptime(max_date, fmt) if max_date else datetime.now()
min_dt = datetime.strptime(min_date, fmt)
invalid = []
for idx, val in series.items():
if pd.isna(val):
continue
try:
dt = datetime.strptime(str(val)[:10], fmt)
if dt < min_dt or dt > max_dt:
invalid.append({"index": idx, "value": val, "reason": "out_of_range"})
except ValueError:
invalid.append({"index": idx, "value": val, "reason": "parse_error"})
return invalid
def verify_code_system(df, code_col, valid_codes, system_name="unknown"):
"""Check that coded values belong to an expected set."""
if code_col not in df.columns:
return {"status": "MISSING_COLUMN", "invalid_count": 0}
codes = df[code_col].dropna().unique()
invalid = [c for c in codes if c not in valid_codes]
return {
"system": system_name, "total_unique": len(codes),
"invalid_codes": invalid, "invalid_count": len(invalid),
"status": "OK" if len(invalid) == 0 else "FAIL",
}Quality check thresholds
质量检查阈值
| Check | Pass | Warning | Fail |
|---|---|---|---|
| Completeness (required field) | ≥ 95% | 80–95% | < 80% |
| Date validity | 0 parse errors | — | Any parse error |
| Date range | All within expected range | — | Future dates or pre-1900 |
| Code system membership | All codes valid | — | Any unrecognized code |
| Duplicate patient IDs | 0 exact duplicates | — | Any duplicate MRN with conflicting demographics |
| 检查项 | 通过 | 警告 | 失败 |
|---|---|---|---|
| 完整性(必填字段) | ≥ 95% | 80–95% | < 80% |
| 日期有效性 | 0 个解析错误 | — | 存在任何解析错误 |
| 日期范围 | 全部处于预期范围内 | — | 存在未来日期或早于 1900 年的日期 |
| 代码系统成员资格 | 所有代码均有效 | — | 存在任何无法识别的代码 |
| 重复患者 ID | 0 个完全重复项 | — | 存在任何 MRN 重复且基本信息冲突的情况 |
6. HL7v2 to FHIR Conceptual Mapping
6. HL7v2 到 FHIR 的概念映射
| HL7v2 Segment | FHIR Resource | Key fields |
|---|---|---|
| PID | Patient | identifier, name, birthDate, gender |
| PV1 | Encounter | class, period, participant |
| OBR + OBX | Observation | code, value, effectiveDateTime, status |
| DG1 | Condition | code, onsetDateTime, clinicalStatus |
| ORC + RXE | MedicationRequest | medicationCodeableConcept, dosageInstruction |
| AL1 | AllergyIntolerance | code, reaction, criticality |
| HL7v2 Segment | FHIR Resource | 关键字段 |
|---|---|---|
| PID | Patient | identifier、name、birthDate、gender |
| PV1 | Encounter | class、period、participant |
| OBR + OBX | Observation | code、value、effectiveDateTime、status |
| DG1 | Condition | code、onsetDateTime、clinicalStatus |
| ORC + RXE | MedicationRequest | medicationCodeableConcept、dosageInstruction |
| AL1 | AllergyIntolerance | code、reaction、criticality |
Common Mistakes
常见错误
-
Wrong: Assuming fixed field positions across all HL7v2 versions without checking for empty segments Right: Always check for empty segments before accessing subfields — field positions are stable but optional fields may be absent Why: Accessing a subfield of an empty segment raises index errors or returns incorrect data
-
Wrong: Splitting HL7v2 messages oninstead of
\nRight: Normalize segment delimiters to\rbefore parsing — many files use\ror\r\nWhy: The HL7v2 standard segment delimiter is carriage return (\n); incorrect splitting produces malformed segments\r -
Wrong: Parsing OBX-5 without checking the value type in OBX-2 Right: Always check OBX-2 first — numeric (NM) and coded (CE) results require different parsing logic Why: Misinterpreting a coded entry as numeric (or vice versa) produces corrupt data
-
Wrong: Treating all OBX segments as lab results Right: Filter by OBR context or OBX-3 code system to distinguish labs from vitals, imaging impressions, and clinical notes Why: OBX carries many observation types; assuming all are labs leads to mixed, unusable datasets
-
Wrong: Hardcoding FHIR resource field paths (e.g., always accessing) Right: Check which choice type variant is present (e.g.,
effectiveDateTimevseffectiveDateTime,effectivePeriodvsvalueQuantity) Why: FHIR allows choice types — hardcoded paths raise AttributeError or miss data when the alternate variant is usedvalueCodeableConcept -
Wrong: Including preliminary Observations () in analysis datasets Right: Filter on
status: "preliminary"for analysis — preliminary results may be superseded Why: Preliminary results can be corrected or replaced, leading to duplicate or inaccurate data in analysesstatus: "final" -
Wrong: Extracting coded values by array position (e.g.,) instead of by system URI Right: Extract by system URI (e.g., filter for
coding[0]) — a Condition may carry both SNOMED and ICD-10 codings Why: Array order is not guaranteed; position-based access returns the wrong code system unpredictably"http://snomed.info/sct" -
Wrong: Processing only the first page of a FHIR Bundle without checking for pagination Right: Checkfor
bundle.linkand follow all pages until exhausted Why: FHIR servers return paginated Bundles — stopping at page one misses most of the datarelation: "next" -
Wrong: Assuming all dates are ISO 8601 format across HL7v2 and FHIR Right: Parse HL7v2 dates asand FHIR dates with variable precision (
YYYYMMDD[HHmmss],2024,2024-01) Why: Format mismatches cause parse errors or silent data corruption2024-01-15 -
Wrong: Accepting coded values without validating the code system URI Right: Verify that thefield matches the expected URI (e.g.,
systemfor LOINC codes) Why: Codes without system URIs are ambiguous — a "6690-2" without system confirmation may not be LOINChttp://loinc.org
-
错误: 在未检查空 segment 的情况下,假定所有 HL7v2 版本中的字段位置都固定 正确: 访问子字段前始终检查 segment 是否为空——字段位置虽然稳定,但可选字段可能不存在 原因: 访问空 segment 的子字段会引发索引错误或返回错误数据
-
错误: 使用而不是
\n拆分 HL7v2 消息 正确: 解析前将 segment 分隔符规范化为\r——许多文件使用\r或\r\n原因: HL7v2 标准中的 segment 分隔符是回车符(\n);拆分方式不正确会生成格式错误的 segment\r -
错误: 解析 OBX-5 前不检查 OBX-2 中的值类型 正确: 始终先检查 OBX-2——数值(NM)和编码值(CE)结果需要不同的解析逻辑 原因: 将编码条目误认为数值(或反之)会破坏数据
-
错误: 将所有 OBX segment 都视为实验室结果 正确: 按 OBR 上下文或 OBX-3 代码系统筛选,以区分实验室结果、生命体征、影像检查结论和临床记录 原因: OBX 可承载多种观察类型;将所有内容都视为实验室结果会混合出不可用的数据集
-
错误: 硬编码 FHIR 资源字段路径(例如,始终访问) 正确: 检查当前存在的是哪种 choice type(例如
effectiveDateTime与effectiveDateTime,或effectivePeriod与valueQuantity) 原因: FHIR 允许 choice type——硬编码路径会引发 AttributeError,或在使用其他变体时遗漏数据valueCodeableConcept -
错误: 将初步 Observation()纳入分析数据集 正确: 分析时按
status: "preliminary"筛选——初步结果可能会被替代 原因: 初步结果可能被更正或替换,从而导致分析中出现重复数据或不准确数据status: "final" -
错误: 按数组位置提取编码值(例如使用),而不是按 system URI 提取 正确: 按 system URI 提取(例如筛选
coding[0])——Condition 可能同时包含 SNOMED 和 ICD-10 编码 原因: 数组顺序不受保证;基于位置访问可能不可预测地返回错误的代码系统"http://snomed.info/sct" -
错误: 处理 FHIR Bundle 时只处理第一页,未检查分页 正确: 检查中是否存在
bundle.link,并持续跟踪所有页面直至遍历完毕 原因: FHIR server 返回分页 Bundle——停在第一页会遗漏大部分数据relation: "next" -
错误: 假定 HL7v2 和 FHIR 中的所有日期均为 ISO 8601 格式 正确: 将 HL7v2 日期解析为,将 FHIR 日期解析为可变精度(
YYYYMMDD[HHmmss]、2024、2024-01) 原因: 格式不匹配会导致解析错误或静默数据损坏2024-01-15 -
错误: 接受编码值但不验证代码系统 URI 正确: 验证字段与预期 URI 匹配(例如 LOINC 代码使用
system) 原因: 没有 system URI 的代码含义不明确——未确认 system 的http://loinc.org未必是 LOINC 代码"6690-2"
References
参考资料
- python-hl7: https://python-hl7.readthedocs.io/
- fhir.resources (Python): https://pypi.org/project/fhir.resources/
- FHIR R4 Specification: https://hl7.org/fhir/R4/
- LOINC: https://loinc.org/
- SNOMED CT: https://www.snomed.org/
- python-hl7:https://python-hl7.readthedocs.io/
- fhir.resources (Python):https://pypi.org/project/fhir.resources/
- FHIR R4 规范:https://hl7.org/fhir/R4/
- LOINC:https://loinc.org/
- SNOMED CT:https://www.snomed.org/